added support for QR payments from BML gateway
Auto Tag on Version Change / check-version (push) Failing after 13s

This commit is contained in:
2026-05-27 21:08:01 +05:00
parent e974a95708
commit a6e7e61b58
5 changed files with 50 additions and 6 deletions
@@ -35,6 +35,39 @@ class BmlQrPayClient {
}
}
/**
* Pre-initiate step required for gateway QR (pay.bml.com.mv).
* POST without channel — expects code 99 (OTP channel selection required).
*/
fun preInitiatePayment(
session: BmlSession,
debitAccount: String,
requestId: String,
amount: Double,
currency: String
): Boolean {
val jo = JSONObject().apply {
put("action", "approve")
put("debitAccount", debitAccount)
put("requestId", requestId)
put("amount", amount)
put("currency", currency)
}
val request = Request.Builder()
.url("$BML_BASE_URL/api/mobile/walletpayments/pay")
.post(jo.toString().toRequestBody("application/json".toMediaType()))
.header("Authorization", "Bearer ${session.accessToken}")
.header("User-Agent", BML_USER_AGENT)
.header("x-app-version", BML_APP_VERSION)
.header("accept", "application/json")
.build()
return client.newCall(request).execute().use { response ->
val body = response.body?.string() ?: return@use false
val json = try { JSONObject(body) } catch (_: Exception) { return@use false }
json.optBoolean("success") && json.optInt("code") == 99
}
}
/**
* Step 1 — initiate: POST with channel but no OTP.
* Returns true when server responds with code 22 (OTP generated).
@@ -40,7 +40,8 @@ class DashboardFragment : Fragment() {
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/")) {
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") ||
raw.startsWith("https://pay.bml.com.mv/app/")) {
(requireActivity() as HomeActivity).navigateTo(
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, pendingQrAccountNumber)
)
@@ -54,8 +54,9 @@ class PayMvQrFragment : Fragment() {
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
// BML card QR — hand off to dedicated payment screen
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/")) {
// BML card/gateway QR — hand off to dedicated payment screen
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") ||
raw.startsWith("https://pay.bml.com.mv/app/")) {
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw))
return@registerForActivityResult
}
@@ -37,7 +37,8 @@ class PayWithCardFragment : Fragment() {
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/")) {
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") ||
raw.startsWith("https://pay.bml.com.mv/app/")) {
(requireActivity() as HomeActivity).navigateTo(
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, pendingQrAccountNumber)
)
@@ -92,6 +92,7 @@ class TransferFragment : Fragment() {
// BML QR merchant payment mode (set when navigated from a card QR scan)
private var bmlQrInfo: BmlQrPayInfo? = null
private var bmlGatewayQr = false // true for pay.bml.com.mv QRs (requires pre-initiate step)
// BML business profile OTP flow state
private enum class BmlOtpState { NONE, SELECTING_CHANNEL, AWAITING_OTP }
@@ -120,8 +121,9 @@ class TransferFragment : Fragment() {
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
// BML card QR — hand off to dedicated payment screen
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/")) {
// BML card/gateway QR — hand off to dedicated payment screen
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") ||
raw.startsWith("https://pay.bml.com.mv/app/")) {
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw))
return@registerForActivityResult
}
@@ -250,6 +252,7 @@ class TransferFragment : Fragment() {
}
private fun lookupBmlQrMerchant(qrUrl: String) {
bmlGatewayQr = qrUrl.startsWith("https://pay.bml.com.mv/app/")
val base64Url = android.util.Base64.encodeToString(
qrUrl.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP)
val app = requireActivity().application as BasedBankApp
@@ -909,6 +912,11 @@ class TransferFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
try {
if (bmlGatewayQr) {
val preOk = BmlQrPayClient().preInitiatePayment(
session, debitAccount, info.requestId, amount, info.currency)
if (!preOk) return@withContext null
}
val initiated = BmlQrPayClient().initiatePayment(
session, debitAccount, info.requestId, amount, info.currency)
if (!initiated) return@withContext null