Adds BML contactless token fetching, APDU emulation utility, TapToPayViewModel, and TapToPayView with NFC animation. Also updates QR scanner, transfer flow, dashboard, and home views. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.0 KiB
Swift
64 lines
2.0 KiB
Swift
import Foundation
|
|
|
|
struct PaymvQrResult: Equatable {
|
|
let account: String
|
|
let amount: String?
|
|
let merchantName: String?
|
|
let purpose: String?
|
|
}
|
|
|
|
enum PaymvQrParser {
|
|
|
|
static func extractBmlGatewayUrl(_ raw: String) -> String? {
|
|
if raw.hasPrefix("https://pay.bml.com.mv/app/") { return raw }
|
|
let root = parseTLV(raw)
|
|
guard let bmlMerchantInfo = root["35"].flatMap({ parseTLV($0) }),
|
|
let inner = bmlMerchantInfo["20"].flatMap({ parseTLV($0) }),
|
|
let url = inner["01"],
|
|
url.hasPrefix("https://pay.bml.com.mv/app/") else { return nil }
|
|
return url
|
|
}
|
|
|
|
static func parse(_ qr: String) -> PaymvQrResult? {
|
|
let tlvs = parseTLV(qr)
|
|
|
|
// Tag 26: merchant account info — sub-tag 03 = account number
|
|
var account: String?
|
|
if let tag26 = tlvs["26"] {
|
|
let sub = parseTLV(tag26)
|
|
account = sub["03"]
|
|
}
|
|
|
|
guard let acc = account, !acc.isEmpty else { return nil }
|
|
|
|
let amount = tlvs["54"]
|
|
let name = tlvs["59"]
|
|
|
|
// Tag 62: additional data — sub-tag 08 = purpose/reference
|
|
var purpose: String?
|
|
if let tag62 = tlvs["62"] {
|
|
let sub = parseTLV(tag62)
|
|
purpose = sub["08"]
|
|
}
|
|
|
|
return PaymvQrResult(account: acc, amount: amount, merchantName: name, purpose: purpose)
|
|
}
|
|
|
|
private static func parseTLV(_ s: String) -> [String: String] {
|
|
var result: [String: String] = [:]
|
|
var idx = s.startIndex
|
|
while s.distance(from: idx, to: s.endIndex) >= 4 {
|
|
let idEnd = s.index(idx, offsetBy: 2)
|
|
let lenEnd = s.index(idEnd, offsetBy: 2)
|
|
let id = String(s[idx..<idEnd])
|
|
guard let len = Int(s[idEnd..<lenEnd]) else { break }
|
|
idx = lenEnd
|
|
guard s.distance(from: idx, to: s.endIndex) >= len else { break }
|
|
let valEnd = s.index(idx, offsetBy: len)
|
|
result[id] = String(s[idx..<valEnd])
|
|
idx = valEnd
|
|
}
|
|
return result
|
|
}
|
|
}
|