fix(payment): validate card expiry date month range and fix PaymentCardView character length

This commit is contained in:
Daniel Arantes Loverde
2026-06-04 15:48:00 -03:00
parent 2809131c25
commit f49e4ea337
2 changed files with 50 additions and 13 deletions

View File

@@ -156,6 +156,9 @@
}, },
"Adicionar loja aos favoritos" : { "Adicionar loja aos favoritos" : {
},
"Adicionar novo cartão" : {
}, },
"Adicionar novo endereço" : { "Adicionar novo endereço" : {
"comment" : "A button label that translates to \"Add new address\" in English.", "comment" : "A button label that translates to \"Add new address\" in English.",
@@ -181,10 +184,6 @@
"comment" : "A text that informs the user that registering implies acceptance of the app's terms of use and privacy policy. The text is clickable and navigates to the respective views when tapped.", "comment" : "A text that informs the user that registering implies acceptance of the app's terms of use and privacy policy. The text is clickable and navigates to the respective views when tapped.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Apenas Pagar" : {
"comment" : "A button that allows the user to pay without saving the payment information.",
"isCommentAutoGenerated" : true
},
"Aplicar" : { "Aplicar" : {
"comment" : "A button that applies a coupon code to a cart.", "comment" : "A button that applies a coupon code to a cart.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
@@ -311,6 +310,9 @@
"Carrinho" : { "Carrinho" : {
"comment" : "A label for the cart section of the app.", "comment" : "A label for the cart section of the app.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
},
"Cartão de Crédito" : {
}, },
"Categorias" : { "Categorias" : {
"comment" : "A heading for the list of available categories in the filters modal.", "comment" : "A heading for the list of available categories in the filters modal.",
@@ -376,10 +378,6 @@
"comment" : "A section header for entering and applying a discount coupon.", "comment" : "A section header for entering and applying a discount coupon.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Dados do Cartão" : {
"comment" : "The header text for the section that collects payment card information.",
"isCommentAutoGenerated" : true
},
"Dark" : { "Dark" : {
"comment" : "Menu item indicating that the appearance should be in dark mode", "comment" : "Menu item indicating that the appearance should be in dark mode",
"extractionState" : "stale", "extractionState" : "stale",
@@ -789,6 +787,12 @@
} }
} }
} }
},
"Novo cartão" : {
},
"Novo Cartão" : {
}, },
"Novo endereço" : { "Novo endereço" : {
"comment" : "A label for a form to add a new address.", "comment" : "A label for a form to add a new address.",
@@ -829,9 +833,8 @@
"comment" : "A label describing the sorting options available in the filters modal.", "comment" : "A label describing the sorting options available in the filters modal.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Pagamento" : { "ou" : {
"comment" : "The title of the screen where a user can enter and save payment information.",
"isCommentAutoGenerated" : true
}, },
"Pagamento via PIX" : { "Pagamento via PIX" : {
"comment" : "The navigation title for the PaymentPixView.", "comment" : "The navigation title for the PaymentPixView.",
@@ -842,6 +845,9 @@
}, },
"Pagar Pelo App" : { "Pagar Pelo App" : {
},
"Pague mais rápido nas próximas compras" : {
}, },
"Pedido #%@" : { "Pedido #%@" : {
@@ -990,6 +996,9 @@
"Salvar Alterações" : { "Salvar Alterações" : {
"comment" : "The text on a button that saves changes to a user's profile.", "comment" : "The text on a button that saves changes to a user's profile.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
},
"Salvar cartão" : {
}, },
"Save" : { "Save" : {
"comment" : "Button title indicating that the current contents should be saved", "comment" : "Button title indicating that the current contents should be saved",
@@ -1020,6 +1029,9 @@
} }
} }
} }
},
"Selecionar Cartão" : {
}, },
"Sem adicionais" : { "Sem adicionais" : {
@@ -1177,6 +1189,9 @@
"Total do Pedido" : { "Total do Pedido" : {
"comment" : "The label above the total price of the order in the payment card view.", "comment" : "The label above the total price of the order in the payment card view.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
},
"Trocar" : {
}, },
"Trocar de loja?" : { "Trocar de loja?" : {
"comment" : "A title for an alert that prompts the user to switch stores.", "comment" : "A title for an alert that prompts the user to switch stores.",
@@ -1185,10 +1200,16 @@
"Trocar Foto" : { "Trocar Foto" : {
"comment" : "A button label that allows a user to change their profile picture.", "comment" : "A button label that allows a user to change their profile picture.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
},
"Trocar Pagamento" : {
}, },
"UPLOAD" : { "UPLOAD" : {
"comment" : "A button label that says \"UPLOAD\".", "comment" : "A button label that says \"UPLOAD\".",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
},
"Vence %@" : {
}, },
"Ver Detalhes" : { "Ver Detalhes" : {
"comment" : "A button that shows order details when pressed.", "comment" : "A button that shows order details when pressed.",

View File

@@ -1116,10 +1116,18 @@ struct PaymentCardView: View {
_cpf = State(initialValue: appState.wrappedValue.profile.cpf) _cpf = State(initialValue: appState.wrappedValue.profile.cpf)
} }
private func validateExpiryDate(digits: Int) -> Bool {
let clean = expiry.filter(\.isNumber)
guard clean.count == digits else { return false }
let monthStr = String(clean.prefix(2))
guard let month = Int(monthStr), (1...12).contains(month) else { return false }
return true
}
var canSubmit: Bool { var canSubmit: Bool {
holderName.trimmingCharacters(in: .whitespaces).isEmpty == false && holderName.trimmingCharacters(in: .whitespaces).isEmpty == false &&
cardNumber.filter(\.isNumber).count >= 13 && cardNumber.filter(\.isNumber).count >= 13 &&
expiry.filter(\.isNumber).count == 6 && validateExpiryDate(digits: 4) &&
cvv.filter(\.isNumber).count >= 3 && cvv.filter(\.isNumber).count >= 3 &&
cpf.filter(\.isNumber).count == 11 cpf.filter(\.isNumber).count == 11
} }
@@ -1665,10 +1673,18 @@ struct ChangePaymentNewCardView: View {
_cpf = State(initialValue: appState.wrappedValue.profile.cpf) _cpf = State(initialValue: appState.wrappedValue.profile.cpf)
} }
private func validateExpiryDate(digits: Int) -> Bool {
let clean = expiry.filter(\.isNumber)
guard clean.count == digits else { return false }
let monthStr = String(clean.prefix(2))
guard let month = Int(monthStr), (1...12).contains(month) else { return false }
return true
}
var canSubmit: Bool { var canSubmit: Bool {
holderName.trimmingCharacters(in: .whitespaces).isEmpty == false && holderName.trimmingCharacters(in: .whitespaces).isEmpty == false &&
cardNumber.filter(\.isNumber).count >= 13 && cardNumber.filter(\.isNumber).count >= 13 &&
expiry.filter(\.isNumber).count == 6 && validateExpiryDate(digits: 6) &&
cvv.filter(\.isNumber).count >= 3 && cvv.filter(\.isNumber).count >= 3 &&
cpf.filter(\.isNumber).count == 11 cpf.filter(\.isNumber).count == 11
} }