feat(payment): identify card brand and display logo dynamically
This commit is contained in:
BIN
pedi-foods/Sources/PediFoods/Resources/alelocard_logo.png
Normal file
BIN
pedi-foods/Sources/PediFoods/Resources/alelocard_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
pedi-foods/Sources/PediFoods/Resources/amexcard_logo.png
Normal file
BIN
pedi-foods/Sources/PediFoods/Resources/amexcard_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
pedi-foods/Sources/PediFoods/Resources/hipercard_logo.png
Normal file
BIN
pedi-foods/Sources/PediFoods/Resources/hipercard_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
pedi-foods/Sources/PediFoods/Resources/mastercard_logo.png
Normal file
BIN
pedi-foods/Sources/PediFoods/Resources/mastercard_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
BIN
pedi-foods/Sources/PediFoods/Resources/sodexo_logo.png
Normal file
BIN
pedi-foods/Sources/PediFoods/Resources/sodexo_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
BIN
pedi-foods/Sources/PediFoods/Resources/visacard_logo.png
Normal file
BIN
pedi-foods/Sources/PediFoods/Resources/visacard_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
@@ -760,6 +760,46 @@ struct PaymentCardView: View {
|
|||||||
@State var latestOrder: PublicOrderResult? = nil
|
@State var latestOrder: PublicOrderResult? = nil
|
||||||
@State var hasOpenedTracking = false
|
@State var hasOpenedTracking = false
|
||||||
|
|
||||||
|
private var detectedBrandLogo: String? {
|
||||||
|
let clean = cardNumber.filter(\.isNumber)
|
||||||
|
guard !clean.isEmpty else { return nil }
|
||||||
|
|
||||||
|
// Sodexo
|
||||||
|
if clean.hasPrefix("506766") || clean.hasPrefix("603389") {
|
||||||
|
return "sodexo_logo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alelo
|
||||||
|
if clean.hasPrefix("5067") || clean.hasPrefix("5078") || clean.hasPrefix("6278") || clean.hasPrefix("6367") {
|
||||||
|
return "alelocard_logo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hipercard
|
||||||
|
if clean.hasPrefix("3841") || clean.hasPrefix("606282") || clean.hasPrefix("637095") || clean.hasPrefix("637568") {
|
||||||
|
return "hipercard_logo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visa
|
||||||
|
if clean.hasPrefix("4") {
|
||||||
|
return "visacard_logo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mastercard
|
||||||
|
if (51...55).contains(Int(clean.prefix(2)) ?? 0) {
|
||||||
|
return "mastercard_logo"
|
||||||
|
}
|
||||||
|
if let prefix4 = Int(clean.prefix(4)), (2221...2720).contains(prefix4) {
|
||||||
|
return "mastercard_logo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amex
|
||||||
|
if clean.hasPrefix("34") || clean.hasPrefix("37") {
|
||||||
|
return "amexcard_logo"
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
VStack(alignment: .leading, spacing: 14) {
|
VStack(alignment: .leading, spacing: 14) {
|
||||||
@@ -786,11 +826,38 @@ struct PaymentCardView: View {
|
|||||||
.foregroundStyle(AppColors.textPrimary)
|
.foregroundStyle(AppColors.textPrimary)
|
||||||
|
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
labeledField("Número do Cartão", placeholder: "0000 0000 0000 0000", text: $cardNumber)
|
labeledField("Número do Cartão", placeholder: "0000 0000 0000 0000", text: $cardNumber, brandLogo: detectedBrandLogo)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.onChange(of: cardNumber) { _, v in
|
||||||
|
let d = String(v.filter(\.isNumber).prefix(16))
|
||||||
|
let masked = stride(from: 0, to: d.count, by: 4)
|
||||||
|
.map { i -> String in
|
||||||
|
let start = d.index(d.startIndex, offsetBy: i)
|
||||||
|
let end = d.index(start, offsetBy: min(4, d.count - i))
|
||||||
|
return String(d[start..<end])
|
||||||
|
}.joined(separator: " ")
|
||||||
|
if masked != v { cardNumber = masked }
|
||||||
|
}
|
||||||
|
|
||||||
labeledField("Nome no Cartão", placeholder: "Como impresso no cartão", text: $cardHolderName)
|
labeledField("Nome no Cartão", placeholder: "Como impresso no cartão", text: $cardHolderName)
|
||||||
|
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
labeledField("Validade", placeholder: "MM/AA", text: $expiry)
|
labeledField("Validade", placeholder: "MM/AA", text: $expiry)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.onChange(of: expiry) { _, v in
|
||||||
|
let d = String(v.filter(\.isNumber).prefix(4))
|
||||||
|
let masked: String
|
||||||
|
if d.count <= 2 { masked = d }
|
||||||
|
else { masked = "\(d.prefix(2))/\(d.dropFirst(2))" }
|
||||||
|
if masked != v { expiry = masked }
|
||||||
|
}
|
||||||
|
|
||||||
labeledField("CVV", placeholder: "•••", text: $cvv)
|
labeledField("CVV", placeholder: "•••", text: $cvv)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.onChange(of: cvv) { _, v in
|
||||||
|
let d = String(v.filter(\.isNumber).prefix(4))
|
||||||
|
if d != v { cvv = d }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(14)
|
.padding(14)
|
||||||
@@ -847,13 +914,21 @@ struct PaymentCardView: View {
|
|||||||
onOpenTracking?()
|
onOpenTracking?()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func labeledField(_ label: String, placeholder: String, text: Binding<String>) -> some View {
|
private func labeledField(_ label: String, placeholder: String, text: Binding<String>, brandLogo: String? = nil) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text(label)
|
Text(label)
|
||||||
.font(AppTypography.caption)
|
.font(AppTypography.caption)
|
||||||
.foregroundStyle(AppColors.textMuted)
|
.foregroundStyle(AppColors.textMuted)
|
||||||
|
HStack(spacing: 8) {
|
||||||
TextField(placeholder, text: text)
|
TextField(placeholder, text: text)
|
||||||
.appNoAutoCap()
|
.appNoAutoCap()
|
||||||
|
if let brandLogo, brandLogo.isEmpty == false {
|
||||||
|
Image(brandLogo)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
.padding(.horizontal, 12)
|
.padding(.horizontal, 12)
|
||||||
.frame(height: 46)
|
.frame(height: 46)
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
|
|||||||
Reference in New Issue
Block a user