diff --git a/app/src/main/java/sh/sar/basedbank/api/bml/BmlQrPayClient.kt b/app/src/main/java/sh/sar/basedbank/api/bml/BmlQrPayClient.kt index 009f9af..aafce82 100644 --- a/app/src/main/java/sh/sar/basedbank/api/bml/BmlQrPayClient.kt +++ b/app/src/main/java/sh/sar/basedbank/api/bml/BmlQrPayClient.kt @@ -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). diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt index 08e702a..8fbef5f 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt @@ -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) ) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt index a2930c7..13aa3d4 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt @@ -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 } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt index 6061a76..0da63f7 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt @@ -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) ) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt index cb4b9b3..8744205 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt @@ -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