forked from thijooree/android
fix BML POS QRs
This commit is contained in:
@@ -9,22 +9,58 @@ data class PaymvQrData(
|
||||
|
||||
object PaymvQrParser {
|
||||
|
||||
private const val BML_GATEWAY_PREFIX = "https://pay.bml.com.mv/app/"
|
||||
private const val BML_EBANKING_PREFIX = "https://ebanking.bankofmaldives.com.mv/qrpay/"
|
||||
private const val BML_POS_DOMAIN = "mv.com.bml.qtr"
|
||||
|
||||
/**
|
||||
* Returns the BML gateway URL if [raw] is or contains one, otherwise null.
|
||||
* Handles both plain URL QRs and combined EMV QRs (e.g. Fahipay+BML card QR).
|
||||
* For combined EMV QRs the URL is parsed from TLV (root tag 35 → sub-tag 20 → sub-sub-tag 01)
|
||||
* rather than via regex, to avoid greedily consuming subsequent EMV tag bytes.
|
||||
* Returns the value to hand to the BML QR payment flow, or null when [raw] is not a BML QR.
|
||||
*
|
||||
* Three shapes exist:
|
||||
* - plain URL QRs — the QR text is the gateway short link or an ebanking `qrpay` link;
|
||||
* - combined EMV QRs (e.g. Fahipay+BML card QRs), which carry the full gateway URL in TLV at
|
||||
* `35` → `20` → `01`;
|
||||
* - BML POS QRs (supplementary data domain `mv.com.bml.qtr`), where the same TLV path holds a
|
||||
* bare reference such as `02:<32 hex>` rather than a URL. Those return the whole EMV payload,
|
||||
* which [bmlPayRequestKey] then reduces back to the reference the lookup wants.
|
||||
*
|
||||
* The TLV is walked rather than regex-matched so a URL cannot greedily swallow the EMV tags
|
||||
* that follow it.
|
||||
*/
|
||||
fun extractBmlGatewayUrl(raw: String): String? {
|
||||
if (raw.startsWith("https://pay.bml.com.mv/app/")) return raw
|
||||
return try {
|
||||
val root = parseTlv(raw)
|
||||
val bmlMerchantInfo = root["35"]?.let { parseTlv(it) } ?: return null
|
||||
val inner = bmlMerchantInfo["20"]?.let { parseTlv(it) } ?: return null
|
||||
inner["01"]?.takeIf { it.startsWith("https://pay.bml.com.mv/app/") }
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
fun bmlQrPayTarget(raw: String): String? {
|
||||
if (raw.startsWith(BML_GATEWAY_PREFIX) || raw.startsWith(BML_EBANKING_PREFIX)) return raw
|
||||
val ref = bmlQrReference(raw) ?: return null
|
||||
if (ref.startsWith("https://")) return ref.takeIf { it.startsWith(BML_GATEWAY_PREFIX) }
|
||||
// A bare reference only means a BML POS QR when the supplementary domain says so; anything
|
||||
// else keeps falling through to the PayMV parse, as it did before POS QRs existed.
|
||||
return raw.takeIf { supplementaryDomain(it)?.startsWith(BML_POS_DOMAIN) == true }
|
||||
}
|
||||
|
||||
/** TLV `80` → `00` — the supplementary data domain (`mv.favara.mpqr`, `mv.com.bml.qtr`, …). */
|
||||
private fun supplementaryDomain(raw: String): String? = try {
|
||||
parseTlv(raw)["80"]?.let { parseTlv(it) }?.get("00")
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
/** TLV `35` → `20` → `01` — the BML payment reference carried inside an EMV QR. */
|
||||
private fun bmlQrReference(raw: String): String? = try {
|
||||
val merchantInfo = parseTlv(raw)["35"]?.let { parseTlv(it) }
|
||||
val inner = merchantInfo?.get("20")?.let { parseTlv(it) }
|
||||
inner?.get("01")?.takeIf { it.isNotBlank() }
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* The `payrequest` lookup key for [target] (a value returned by [bmlQrPayTarget]), to be
|
||||
* Base64-encoded into the URL. URL QRs resolve under the URL itself; POS QRs resolve under the
|
||||
* bare `35` → `20` → `01` reference (the whole EMV payload is rejected with code 112,
|
||||
* "Unsupported payment link").
|
||||
*/
|
||||
fun bmlPayRequestKey(target: String): String {
|
||||
if (target.startsWith("https://")) return target
|
||||
return bmlQrReference(target) ?: target
|
||||
}
|
||||
|
||||
fun parse(raw: String): PaymvQrData? {
|
||||
|
||||
Reference in New Issue
Block a user