added support for QR payments from BML gateway
Auto Tag on Version Change / check-version (push) Failing after 13s
Auto Tag on Version Change / check-version (push) Failing after 13s
This commit is contained in:
@@ -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.
|
* Step 1 — initiate: POST with channel but no OTP.
|
||||||
* Returns true when server responds with code 22 (OTP generated).
|
* Returns true when server responds with code 22 (OTP generated).
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ class DashboardFragment : Fragment() {
|
|||||||
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: 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(
|
(requireActivity() as HomeActivity).navigateTo(
|
||||||
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, pendingQrAccountNumber)
|
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, pendingQrAccountNumber)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ class PayMvQrFragment : Fragment() {
|
|||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
||||||
|
|
||||||
// BML card QR — hand off to dedicated payment screen
|
// BML card/gateway QR — hand off to dedicated payment screen
|
||||||
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))
|
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw))
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ class PayWithCardFragment : Fragment() {
|
|||||||
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: 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(
|
(requireActivity() as HomeActivity).navigateTo(
|
||||||
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, pendingQrAccountNumber)
|
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)
|
// BML QR merchant payment mode (set when navigated from a card QR scan)
|
||||||
private var bmlQrInfo: BmlQrPayInfo? = null
|
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
|
// BML business profile OTP flow state
|
||||||
private enum class BmlOtpState { NONE, SELECTING_CHANNEL, AWAITING_OTP }
|
private enum class BmlOtpState { NONE, SELECTING_CHANNEL, AWAITING_OTP }
|
||||||
@@ -120,8 +121,9 @@ class TransferFragment : Fragment() {
|
|||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
||||||
|
|
||||||
// BML card QR — hand off to dedicated payment screen
|
// BML card/gateway QR — hand off to dedicated payment screen
|
||||||
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))
|
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw))
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
@@ -250,6 +252,7 @@ class TransferFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun lookupBmlQrMerchant(qrUrl: String) {
|
private fun lookupBmlQrMerchant(qrUrl: String) {
|
||||||
|
bmlGatewayQr = qrUrl.startsWith("https://pay.bml.com.mv/app/")
|
||||||
val base64Url = android.util.Base64.encodeToString(
|
val base64Url = android.util.Base64.encodeToString(
|
||||||
qrUrl.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP)
|
qrUrl.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP)
|
||||||
val app = requireActivity().application as BasedBankApp
|
val app = requireActivity().application as BasedBankApp
|
||||||
@@ -909,6 +912,11 @@ class TransferFragment : Fragment() {
|
|||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
if (bmlGatewayQr) {
|
||||||
|
val preOk = BmlQrPayClient().preInitiatePayment(
|
||||||
|
session, debitAccount, info.requestId, amount, info.currency)
|
||||||
|
if (!preOk) return@withContext null
|
||||||
|
}
|
||||||
val initiated = BmlQrPayClient().initiatePayment(
|
val initiated = BmlQrPayClient().initiatePayment(
|
||||||
session, debitAccount, info.requestId, amount, info.currency)
|
session, debitAccount, info.requestId, amount, info.currency)
|
||||||
if (!initiated) return@withContext null
|
if (!initiated) return@withContext null
|
||||||
|
|||||||
Reference in New Issue
Block a user