fix(api): map API response error payload and validate card saving synchronously
This commit is contained in:
@@ -111,6 +111,11 @@ private extension ApiClient {
|
|||||||
guard let data = responseString.data(using: .utf8) else {
|
guard let data = responseString.data(using: .utf8) else {
|
||||||
throw NetworkError.decodeError("Resposta nao UTF-8")
|
throw NetworkError.decodeError("Resposta nao UTF-8")
|
||||||
}
|
}
|
||||||
|
if let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||||
|
let errorFlag = object["error"] as? Bool, errorFlag {
|
||||||
|
let message = object["message"] as? String ?? object["msg"] as? String
|
||||||
|
throw NetworkError.httpError(200, message ?? "Erro no servidor")
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
return try JSONDecoder().decode(T.self, from: data)
|
return try JSONDecoder().decode(T.self, from: data)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -247,6 +252,12 @@ private extension ApiClient {
|
|||||||
throw NetworkError.unauthorized(payload?.message)
|
throw NetworkError.unauthorized(payload?.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||||
|
let errorFlag = object["error"] as? Bool, errorFlag {
|
||||||
|
let message = object["message"] as? String ?? object["msg"] as? String
|
||||||
|
throw NetworkError.httpError(http.statusCode, message ?? "Erro no servidor")
|
||||||
|
}
|
||||||
|
|
||||||
if http.statusCode == 429 {
|
if http.statusCode == 429 {
|
||||||
let retryAfter = Int(http.value(forHTTPHeaderField: "Retry-After") ?? "")
|
let retryAfter = Int(http.value(forHTTPHeaderField: "Retry-After") ?? "")
|
||||||
throw NetworkError.rateLimited(retryAfter)
|
throw NetworkError.rateLimited(retryAfter)
|
||||||
@@ -309,6 +320,14 @@ private extension ApiClient {
|
|||||||
if let code = payload?.code, code.isEmpty == false {
|
if let code = payload?.code, code.isEmpty == false {
|
||||||
return "Erro: \(code)"
|
return "Erro: \(code)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fallback if error true is present but without a classic structure
|
||||||
|
if let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||||
|
let errorFlag = object["error"] as? Bool, errorFlag {
|
||||||
|
if let msg = object["message"] as? String ?? object["msg"] as? String {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
}
|
||||||
return String(data: data, encoding: .utf8)
|
return String(data: data, encoding: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,9 +337,10 @@ private extension ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
if let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||||
|
let errorFlag = object["error"] as? Bool ?? false
|
||||||
let code = object["code"] as? String
|
let code = object["code"] as? String
|
||||||
let message = object["message"] as? String
|
let message = object["message"] as? String ?? object["msg"] as? String ?? object["error_description"] as? String
|
||||||
if code != nil || message != nil {
|
if errorFlag || code != nil || message != nil {
|
||||||
return ApiErrorDescriptor(code: code, message: message)
|
return ApiErrorDescriptor(code: code, message: message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,14 +364,42 @@ private extension ApiClient {
|
|||||||
|
|
||||||
#if canImport(LCEssentials) && os(iOS)
|
#if canImport(LCEssentials) && os(iOS)
|
||||||
func serverMessage(from error: NSError) -> String? {
|
func serverMessage(from error: NSError) -> String? {
|
||||||
if let reason = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String,
|
// Tenta buscar no userInfo por chaves conhecidas
|
||||||
!reason.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
for key in [NSLocalizedFailureReasonErrorKey, "body", "responseBody", "data", "message"] {
|
||||||
if let data = reason.data(using: .utf8),
|
if let value = error.userInfo[key] as? String,
|
||||||
let parsed = serverMessage(from: data),
|
!value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
if let data = value.data(using: .utf8),
|
||||||
return parsed
|
let parsed = serverMessage(from: data),
|
||||||
|
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
if parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
if !value.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
} else if let valueData = error.userInfo[key] as? Data {
|
||||||
|
if let parsed = serverMessage(from: valueData),
|
||||||
|
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||||
|
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tenta varrer qualquer chave do userInfo por strings que possam ser JSON ou mensagens
|
||||||
|
for (_, value) in error.userInfo {
|
||||||
|
if let str = value as? String, !str.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
if str.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
|
||||||
|
if let data = str.data(using: .utf8),
|
||||||
|
let parsed = serverMessage(from: data),
|
||||||
|
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||||
|
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return reason
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let description = error.userInfo[NSLocalizedDescriptionKey] as? String,
|
if let description = error.userInfo[NSLocalizedDescriptionKey] as? String,
|
||||||
@@ -364,10 +412,24 @@ private extension ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func serverPayload(from error: NSError) -> ApiErrorDescriptor? {
|
func serverPayload(from error: NSError) -> ApiErrorDescriptor? {
|
||||||
if let reason = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String,
|
for key in [NSLocalizedFailureReasonErrorKey, "body", "responseBody", "data"] {
|
||||||
let data = reason.data(using: .utf8),
|
if let value = error.userInfo[key] as? String,
|
||||||
let payload = serverPayload(from: data) {
|
let data = value.data(using: .utf8),
|
||||||
return payload
|
let payload = serverPayload(from: data) {
|
||||||
|
return payload
|
||||||
|
} else if let valueData = error.userInfo[key] as? Data,
|
||||||
|
let payload = serverPayload(from: valueData) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, value) in error.userInfo {
|
||||||
|
if let str = value as? String,
|
||||||
|
str.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{"),
|
||||||
|
let data = str.data(using: .utf8),
|
||||||
|
let payload = serverPayload(from: data) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1332,9 +1332,7 @@ struct PaymentCardView: View {
|
|||||||
nickname: nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : nickname,
|
nickname: nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : nickname,
|
||||||
isDefault: false
|
isDefault: false
|
||||||
)
|
)
|
||||||
Task<Void, Never> {
|
_ = try await ApiService().saveCard(payload: savePayload)
|
||||||
do { _ = try await ApiService().saveCard(payload: savePayload) } catch {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = try await ApiService().createOrder(storeId: context.storeId, payload: payload)
|
let response = try await ApiService().createOrder(storeId: context.storeId, payload: payload)
|
||||||
@@ -1761,12 +1759,11 @@ struct ChangePaymentNewCardView: View {
|
|||||||
appState.profile.cpf = cleanCpf
|
appState.profile.cpf = cleanCpf
|
||||||
Task<Void, Never> { do { _ = try await ApiService().updateProfileCpf(cpf: cleanCpf) } catch {} }
|
Task<Void, Never> { do { _ = try await ApiService().updateProfileCpf(cpf: cleanCpf) } catch {} }
|
||||||
}
|
}
|
||||||
if saveCard {
|
|
||||||
let sp = SaveCardPayload(creditCard: SaveCardCreditCardPayload(holderName: holderName, number: cleanNumber, expiryMonth: expiryMonth, expiryYear: expiryYear, ccv: cvv), creditCardHolderInfo: holderInfo, nickname: nickname.trimmingCharacters(in: .whitespaces).isEmpty ? nil : nickname, isDefault: false)
|
|
||||||
Task<Void, Never> { do { _ = try await ApiService().saveCard(payload: sp) } catch {} }
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
if saveCard {
|
||||||
|
let sp = SaveCardPayload(creditCard: SaveCardCreditCardPayload(holderName: holderName, number: cleanNumber, expiryMonth: expiryMonth, expiryYear: expiryYear, ccv: cvv), creditCardHolderInfo: holderInfo, nickname: nickname.trimmingCharacters(in: .whitespaces).isEmpty ? nil : nickname, isDefault: false)
|
||||||
|
_ = try await ApiService().saveCard(payload: sp)
|
||||||
|
}
|
||||||
let response = try await ApiService().changePaymentMethod(storeId: pixContext.storeId, orderId: pixContext.orderId, payload: payload)
|
let response = try await ApiService().changePaymentMethod(storeId: pixContext.storeId, orderId: pixContext.orderId, payload: payload)
|
||||||
if response.error { SnackbarCenter.shared.show(title: response.message ?? "Pagamento recusado.", style: .error, icon: "xmark.octagon.fill", duration: 4.0); return }
|
if response.error { SnackbarCenter.shared.show(title: response.message ?? "Pagamento recusado.", style: .error, icon: "xmark.octagon.fill", duration: 4.0); return }
|
||||||
onConfirmed(pixContext.orderId, pixContext.shortId)
|
onConfirmed(pixContext.orderId, pixContext.shortId)
|
||||||
|
|||||||
Reference in New Issue
Block a user