From 237d25af67b44ae213de6bb999c44c9b003c9335 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Sat, 3 Oct 2026 00:26:00 +0500 Subject: [PATCH] add dhiraagu bill pay --- ...loadClient.kt => DhiraaguPaymentClient.kt} | 100 ++++++++++--- .../transfer/CardPayoutTransferHandler.kt | 23 ++- docs/README.md | 2 +- docs/bmlapi/16-card-payment.md | 2 +- docs/dhiraaguapi/02-reload.md | 2 +- docs/dhiraaguapi/03-bill-pay.md | 132 ++++++++++++++++++ docs/dhiraaguapi/README.md | 1 + docs/thijooree/07-transfer.md | 10 +- docs/thijooree/20-transfer-flows.md | 14 +- ...card-verification-and-merchant-card-pay.md | 2 +- 10 files changed, 251 insertions(+), 37 deletions(-) rename app/src/main/java/sh/sar/basedbank/api/dhiraagu/{DhiraaguReloadClient.kt => DhiraaguPaymentClient.kt} (56%) create mode 100644 docs/dhiraaguapi/03-bill-pay.md diff --git a/app/src/main/java/sh/sar/basedbank/api/dhiraagu/DhiraaguReloadClient.kt b/app/src/main/java/sh/sar/basedbank/api/dhiraagu/DhiraaguPaymentClient.kt similarity index 56% rename from app/src/main/java/sh/sar/basedbank/api/dhiraagu/DhiraaguReloadClient.kt rename to app/src/main/java/sh/sar/basedbank/api/dhiraagu/DhiraaguPaymentClient.kt index a48ec99..65e9b87 100644 --- a/app/src/main/java/sh/sar/basedbank/api/dhiraagu/DhiraaguReloadClient.kt +++ b/app/src/main/java/sh/sar/basedbank/api/dhiraagu/DhiraaguPaymentClient.kt @@ -13,14 +13,14 @@ import java.util.Locale import java.util.concurrent.TimeUnit /** - * Dhiraagu prepaid reload through the dhiraagu.com.mv "Easy TopUp" page, paid by card on BML's - * merchant gateway. Dhiraagu only builds the order; the money moves on the BML Merchant Services - * transaction [createBmlTransaction] hands back, which is paid like any card-only BML merchant - * link. See `docs/dhiraaguapi/02-reload.md`. + * Dhiraagu prepaid reload ("Easy TopUp") and bill payment ("Easy Pay") through dhiraagu.com.mv, + * paid by card on BML's merchant gateway. Dhiraagu only builds the order; the money moves on the + * BML Merchant Services transaction handed back, which is paid like any card-only BML merchant + * link. See `docs/dhiraaguapi/02-reload.md` and `docs/dhiraaguapi/03-bill-pay.md`. * * Every call blocks, so run it on an IO thread. */ -class DhiraaguReloadClient { +class DhiraaguPaymentClient { private val client = OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) @@ -33,11 +33,11 @@ class DhiraaguReloadClient { * amount paid, GST included. Returns the 24-hex BML transaction id. Throws with Dhiraagu's * wording when a step is refused. */ - fun createBmlTransaction(number: String, amount: Int): String { + fun createReloadTransaction(number: String, amount: Int): String { val topupNonce = pageNonce("$BASE/services/easy-topup") val gst = gstOf(amount) val cart = api("cart", "recharge", topupNonce, JSONObject() - .put("formId", FORM_ID) + .put("formId", FORM_RELOAD) .put("serviceNumber", number) .put("amount", amount) .put("amountGST", gst.toDouble()) @@ -46,21 +46,66 @@ class DhiraaguReloadClient { .put("memberId", "").put("memberName", "").put("memberNId", "") .put("customerId", "").put("customerCode", "") .put("version", 2)) + return payCart(FORM_RELOAD, cart, BigDecimal(amount)) + } + + /** + * Creates the bill payment order for postpaid [number] and the BML transaction paying for + * it: lookup (for the billing account) → cart → merchant → payment → BML transaction. + * [amount] is MVR, up to 2 decimal places. Returns the 24-hex BML transaction id. Throws + * with Dhiraagu's wording when a step is refused. + */ + fun createBillPayTransaction(number: String, amount: BigDecimal): String { + val easyPayNonce = pageNonce("$BASE/services/easy-pay") + // The cart needs the billing account the number belongs to, which only the lookup gives + val info = call("dhiraaguIO", "infoUnlisted", easyPayNonce, JSONObject().put("number", number)) + if (info.optJSONArray("serviceDetails")?.optJSONObject(0)?.optString("prepaidIndicator") == "Y") { + throw Exception("Prepaid number is not allowed.") + } + val accountNumber = info.optString("accountNumber").ifBlank { throw Exception("Invalid account/service number") } + + // The page refuses some account statuses and customer types before ordering; so do we + val rules = runCatching { get("setting", "bill", easyPayNonce).getJSONObject("resp").getJSONObject("settingAppJson1") }.getOrNull() + val status = info.optString("accountStatus") + if (rules != null && status in rules.blockedValues("accountStatus")) { + throw Exception("Dhiraagu can't accept payment for this service. Contact Dhiraagu customer service. [Account Status: $status]") + } + val customerType = info.optString("customerType") + if (rules != null && customerType in rules.blockedValues("customerType")) { + throw Exception("The number is not allowed. [Customer Type: $customerType]") + } + + val cart = api("cart", "easyPay", easyPayNonce, JSONObject() + .put("formId", FORM_BILL) + .put("serviceNumber", number) + .put("accountNumber", accountNumber) + .put("amount", money(amount)) + .put("memberId", "").put("memberName", "").put("memberNId", "") + .put("billRef", "") + .put("billType", if (info.optString("type") == BILL_WRITE_OFF) BILL_WRITE_OFF else BILL_PAYMENT)) + return payCart(FORM_BILL, cart, amount) + } + + /** + * The payment page's half, shared by every form: picks the BML gateway, creates the payment + * for [cart] and has Dhiraagu create the BML transaction. Returns its 24-hex id. + */ + private fun payCart(formId: Int, cart: JSONObject, amount: BigDecimal): String { val cartId = cart.optString("cartId").ifBlank { throw Exception("Dhiraagu didn't create the order") } // The payment page carries its own nonce, used for the rest of the order val paymentNonce = pageNonce("$BASE/services/payment-v2?cartid=$cartId") - val merchants = apiList("merchant", "form", paymentNonce, JSONObject().put("formId", FORM_ID)) + val merchants = apiList("merchant", "form", paymentNonce, JSONObject().put("formId", formId)) val bml = (0 until merchants.length()).map { merchants.getJSONObject(it) } .firstOrNull { it.optInt("gatewayId") == GATEWAY_BML } ?: throw Exception("Dhiraagu isn't taking BML card payments right now") val payment = api("payment", "create", paymentNonce, JSONObject() - .put("formId", FORM_ID) + .put("formId", formId) .put("cartId", cartId) .put("gatewayId", GATEWAY_BML) .put("dhiraaguPayNumber", "") - .put("amount", String.format(Locale.US, "%.2f", amount.toDouble())) + .put("amount", money(amount)) .put("paymentMerchantId", bml.getString("merchantId")) .put("memberId", "").put("tokenize", "").put("paymentType", "") .put("recurringFrequency", "").put("bmlTokenId", "")) @@ -71,13 +116,22 @@ class DhiraaguReloadClient { ?: throw Exception("BML didn't create the transaction") } + private fun money(amount: BigDecimal) = + String.format(Locale.US, "%.2f", amount.setScale(2, RoundingMode.HALF_UP)) + + /** The `val` list of a `setting` rule, e.g. `{"accountStatus":{"val":["F"]}}`. */ + private fun JSONObject.blockedValues(rule: String): Set { + val vals = optJSONObject(rule)?.optJSONArray("val") ?: return emptySet() + return (0 until vals.length()).map { vals.optString(it) }.toSet() + } + // ── HTTP ───────────────────────────────────────────────────────────────── /** Every page embeds a `var nonce = "…"` that its API calls send as the `nonce` header. */ private fun pageNonce(url: String): String = - NONCE.find(get(url))?.groupValues?.get(1) ?: throw Exception("Dhiraagu page didn't load") + NONCE.find(page(url))?.groupValues?.get(1) ?: throw Exception("Dhiraagu page didn't load") - private fun get(url: String): String = client.newCall( + private fun page(url: String): String = client.newCall( Request.Builder().url(url) .header("User-Agent", UA) .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") @@ -94,10 +148,16 @@ class DhiraaguReloadClient { call(sub, act, nonce, body).getJSONArray("resp") /** POSTs to `sdk-dhr-webapi.ashx`; throws unless `respStatus` is OK. */ - private fun call(sub: String, act: String, nonce: String, body: JSONObject): JSONObject { + private fun call(sub: String, act: String, nonce: String, body: JSONObject): JSONObject = + send(sub, act, nonce, body.toString().toRequestBody(JSON)) + + /** GETs from `sdk-dhr-webapi.ashx` (the settings calls); throws unless `respStatus` is OK. */ + private fun get(sub: String, act: String, nonce: String): JSONObject = send(sub, act, nonce, null) + + private fun send(sub: String, act: String, nonce: String, body: okhttp3.RequestBody?): JSONObject { val text = client.newCall( Request.Builder().url("$API?website_id=$WEBSITE_ID&sub=$sub&act=$act") - .post(body.toString().toRequestBody(JSON)) + .apply { if (body != null) post(body) } .header("User-Agent", UA) .header("Accept", "application/json, text/javascript, */*; q=0.01") .header("X-Requested-With", "XMLHttpRequest") @@ -112,7 +172,7 @@ class DhiraaguReloadClient { throw Exception("Unexpected response from Dhiraagu") } if (obj.optString("respStatus") != "OK") { - throw Exception(obj.optString("respMsg").ifBlank { obj.optString("resp") }.ifBlank { "Dhiraagu refused the reload" }) + throw Exception(obj.optString("respMsg").ifBlank { obj.optString("resp") }.ifBlank { "Dhiraagu refused the payment" }) } return obj } @@ -126,14 +186,20 @@ class DhiraaguReloadClient { private val NONCE = Regex("""var nonce = "([^"]+)"""") private val TXN_URL = Regex("""transaction\.merchants\.bankofmaldives\.com\.mv/([0-9a-fA-F]{24})""") + /** Easy Pay's (bill payment) form id across cart / merchant / payment. */ + private const val FORM_BILL = 1 /** Easy TopUp's form id across cart / merchant / payment. */ - private const val FORM_ID = 2 + private const val FORM_RELOAD = 2 /** Bank of Maldives in `merchant&act=form` (1 = BML, 2 = MIB, 3 = DhiraaguPay). */ private const val GATEWAY_BML = 1 private const val GST_RATE = 0.08 + /** Easy Pay's `billType`s; the lookup's `type` says which applies. */ + private const val BILL_PAYMENT = "BillPayment" + private const val BILL_WRITE_OFF = "writeOffPayments" + /** - * The GST inside a GST-inclusive [amount], as the page works it out: + * The GST inside a GST-inclusive reload [amount], as the page works it out: * `amount × rate / (1 + rate)`, to 2 places. The number is credited `amount − gst`. */ fun gstOf(amount: Int): BigDecimal { diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/transfer/CardPayoutTransferHandler.kt b/app/src/main/java/sh/sar/basedbank/ui/home/transfer/CardPayoutTransferHandler.kt index 8219309..a5709c4 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/transfer/CardPayoutTransferHandler.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/transfer/CardPayoutTransferHandler.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.withContext import sh.sar.basedbank.R import sh.sar.basedbank.api.bml.BmlMerchantTxnClient import sh.sar.basedbank.api.dhiraagu.DhiraaguClient -import sh.sar.basedbank.api.dhiraagu.DhiraaguReloadClient +import sh.sar.basedbank.api.dhiraagu.DhiraaguPaymentClient import sh.sar.basedbank.databinding.FragmentTransferBinding import sh.sar.basedbank.ui.home.HomeViewModel import sh.sar.basedbank.ui.home.TransferFragment @@ -20,8 +20,8 @@ import java.math.RoundingMode * A carrier service a verified BML card can pay, through the carrier's own website and its BML * merchant gateway. The limits are the carrier website's, not Fahipay's. * - * Only Dhiraagu reload so far. Add the others as constants once their send path lands; the - * exhaustive `when`s over this enum point at every site that needs updating. + * Only Dhiraagu so far. Add the others as constants once their send path lands; the exhaustive + * `when`s over this enum point at every site that needs updating. */ enum class CardPayoutService( override val label: String, @@ -36,8 +36,11 @@ enum class CardPayoutService( minAmount = 20, maxAmount = 1000, decimalsAllowed = false, gstPercent = 8) { // Dhiraagu rounds the GST to 2 places and credits the rest override fun creditedAfterGst(amount: BigDecimal): BigDecimal = - amount - DhiraaguReloadClient.gstOf(amount.setScale(0, RoundingMode.DOWN).toInt()) - }; + amount - DhiraaguPaymentClient.gstOf(amount.setScale(0, RoundingMode.DOWN).toInt()) + }, + // Easy Pay sets no limits of its own: any amount with up to 2 decimals, no GST + DHIRAAGU_BILL("Dhiraagu Bill Pay", "Dhiraagu · Bill Pay", R.drawable.dhiraagu_logo, + minAmount = 1, maxAmount = null, decimalsAllowed = true, gstPercent = null); } /** @@ -78,7 +81,11 @@ class CardPayoutTransferHandler( fun typesFor(result: CarrierLookup.Result, cards: Set): List { if (cards.isEmpty()) return emptyList() return buildList { - if (result.dhiraagu.type == DhiraaguClient.CustType.RELOAD) add(CardPayoutService.DHIRAAGU_RELOAD) + when (result.dhiraagu.type) { + DhiraaguClient.CustType.RELOAD -> add(CardPayoutService.DHIRAAGU_RELOAD) + DhiraaguClient.CustType.BILL_PAY -> add(CardPayoutService.DHIRAAGU_BILL) + DhiraaguClient.CustType.UNSUPPORTED -> {} + } }.map { TransferType.Card(it, result.ownerName, cards) } } @@ -144,7 +151,9 @@ class CardPayoutTransferHandler( runCatching { val txnId = when (svc) { CardPayoutService.DHIRAAGU_RELOAD -> - DhiraaguReloadClient().createBmlTransaction(number, amount.intValueExact()) + DhiraaguPaymentClient().createReloadTransaction(number, amount.intValueExact()) + CardPayoutService.DHIRAAGU_BILL -> + DhiraaguPaymentClient().createBillPayTransaction(number, amount) } BmlMerchantTxnClient().fetchPayPage(txnId) } diff --git a/docs/README.md b/docs/README.md index 6dcf2d9..7c814fe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,5 +17,5 @@ | [bmlapi/](bmlapi/README.md) | Bank of Maldives — hybrid web/OAuth login, dashboard, transfers, cards, QR payments, tap-to-pay | | [mibapi/](mibapi/README.md) | MIB Faisanet — Blowfish-encrypted API + WebView session, accounts, transfers, contacts | | [fahipayapi/](fahipayapi/README.md) | Fahipay digital wallet — login, balance, history, contacts | -| [dhiraaguapi/](dhiraaguapi/README.md) | Dhiraagu Easy Pay / Easy TopUp — number lookup, reload by BML card | +| [dhiraaguapi/](dhiraaguapi/README.md) | Dhiraagu Easy Pay / Easy TopUp — number lookup, reload and bill pay by BML card | | [ooredooapi/](ooredooapi/README.md) | Ooredoo Quick Pay — number validation for Raastas / bill pay | diff --git a/docs/bmlapi/16-card-payment.md b/docs/bmlapi/16-card-payment.md index 227bbb7..6b42b78 100644 --- a/docs/bmlapi/16-card-payment.md +++ b/docs/bmlapi/16-card-payment.md @@ -235,7 +235,7 @@ merchant's own receipt page: ``` GET transaction…/?wait=1 → 302 https://www.dhiraagu.com.mv/api/dhiraagu-bml-response.aspx?transactionId=&state=CONFIRMED&signature= -→ 302 https://www.dhiraagu.com.mv/services/reload-receipt?pyid= +→ 302 https://www.dhiraagu.com.mv/services/reload-receipt?pyid= (bill pay: /services/bill-receipt) ``` (FahiPay's is `fahipay.mv/api/bml/gateway/callback/?…state=CONFIRMED`.) diff --git a/docs/dhiraaguapi/02-reload.md b/docs/dhiraaguapi/02-reload.md index a9ddf6e..5cf634f 100644 --- a/docs/dhiraaguapi/02-reload.md +++ b/docs/dhiraaguapi/02-reload.md @@ -172,4 +172,4 @@ UA, so these calls are made the same way. **Related:** [Number Lookup](01-number-lookup.md) · [BML Merchant Card Payment](../bmlapi/16-card-payment.md) · App side: [Transfer Flows](../thijooree/20-transfer-flows.md#carrier-services-by-bml-card) -[← Number Lookup](01-number-lookup.md) +[← Number Lookup](01-number-lookup.md) · [Bill Pay →](03-bill-pay.md) diff --git a/docs/dhiraaguapi/03-bill-pay.md b/docs/dhiraaguapi/03-bill-pay.md new file mode 100644 index 0000000..7fcf450 --- /dev/null +++ b/docs/dhiraaguapi/03-bill-pay.md @@ -0,0 +1,132 @@ +# Bill Pay (Easy Pay, paid by BML card) + +Pay a Dhiraagu postpaid bill through the dhiraagu.com.mv **Easy Pay** page. Like +[Reload](02-reload.md), Dhiraagu only builds the order and the money moves on a **BML Merchant +Services transaction** paid by card + 3-D Secure +([BML API → Merchant Card Payment](../bmlapi/16-card-payment.md)). From the payment page on, +the two flows are identical; only the first page, the cart call and the form id differ. + +Reconstructed from `docs/dhiraaguapi/tmp/dhiraagu_billpay_gateway.har` (a Firefox HAR) and the +Easy Pay page's inline script. + +--- + +## Flow overview + +``` +GET /services/easy-pay → nonce #1 +GET setting&act=bill (nonce #1) → blocked account statuses / customer types +POST dhiraaguIO&act=infoUnlisted (nonce #1) → accountNumber, type, accountStatus, customerType +POST cart&act=easyPay (nonce #1) → cartId +GET /services/payment-v2?cartid= → nonce #2 +POST merchant&act=form {"formId":1} → BML gateway's merchantId +POST payment&act=create (formId 1) → paymentId, oid (EP…) +POST bml&act=createV2 → BML transaction url + ── from here: the BML card-only merchant flow ── +GET transaction.merchants…/?wait=1 → 302 dhiraagu-bml-response.aspx (posts the payment) + → 302 /services/bill-receipt?pyid= +``` + +As with reload, the `?wait=1` return hop is what tells Dhiraagu it was paid. + +Common headers and the `{"respStatus":"OK","resp":…}` envelope are as in +[Reload → Common](02-reload.md#common). + +--- + +## 1. Settings + +`GET …&sub=setting&act=bill` (no body, so a GET) — rules the page checks the lookup against: + +```json +{"settingAppJson1":{"accountStatus":{"val":["F"],…},"customerType":{"val":["P"],…}}} +``` + +A number whose `accountStatus` or `customerType` is in a `val` list is refused before ordering +("Payment for this service could not be accepted… [Account Status: F]" / "The number is not +allowed. [Customer Type: P]"). Thijooree applies the same rules, skipping them if the call fails. + +`setting&act=maintenance` has `public.easyPay` — `"Y"` means the page is under maintenance. +Not checked. + +--- + +## 2. Lookup + +`sub=dhiraaguIO&act=infoUnlisted`, `{"number":"7XXXXXX"}` — the same call as +[Number Lookup](01-number-lookup.md), but the bill payment needs more of its answer: + +```json +{"respStatus":"OK","accountNumber":"1466154","accountStatus":"W","customerType":"S", + "type":"BillPayment","serviceDetails":[{"unlisted":"N","prepaidIndicator":"N"}], + "accountOwnerInfo":{"name":"…"}} +``` + +Note the fields are at the top level, not under `resp`. + +| Field | Use | +|---|---| +| `accountNumber` | the billing account the cart is made out to | +| `type` | `BillPayment`, or `writeOffPayments` for a written-off account — sent as `billType` | +| `prepaidIndicator` | `"Y"` is refused ("Prepaid number is not allowed.") | + +The page also accepts the account number itself in place of a service number (then +`serviceNumber` is sent empty); Thijooree only pays by phone number. + +--- + +## 3. Cart + +`sub=cart&act=easyPay`, nonce from `GET /services/easy-pay`. + +```json +{"formId":1,"serviceNumber":"7XXXXXX","accountNumber":"1466154","amount":"1.05", + "memberId":"","memberName":"","memberNId":"","billRef":"","billType":"BillPayment"} +``` +```json +{"cartId":"8fc33fa4-…","formId":1,"cartJson":[{"accountNumber":"1466154","serviceNumber":"7XXXXXX", + "amount":1.05,"billRef":"","billType":"BillPayment"}],"cartAmount":1.05,"cartExpiry":"…(20 min)…", + "paymentUrl":"https://www.dhiraagu.com.mv/services/payment-v2?cartid=8fc33fa4-…", …} +``` + +| Rule | Value | +|---|---| +| Amount | any positive amount, up to 2 decimal places (the page's only check). No min / max. | +| GST | none | + +--- + +## 4. Payment page + +Same as [Reload §3–5](02-reload.md#3-payment-gateway) with `formId: 1`: + +- `merchant&act=form` lists DhiraaguPay (3), Bank of Maldives (1) and MIB (2) for "Easy Pay". + The BML `merchantId` is the same as reload's. +- `payment&act=create` returns an `oid` starting `EP` (reload's start `ET`). +- `bml&act=createV2` returns the transaction with `"customerReference":"WebApp - Easy Pay"` and + the same `redirectUrl`. Its page is card-only, no BML Pay. + +--- + +## Bill record + +`sub=bill&act=list`, `{"paymentId"}`, nonce from the receipt page — what the receipt shows: + +```json +[{"oid":"EP20260006911918","transId":"","accountNumber":"1466154","serviceNumber":"7XXXXXX", + "amount":1.05,"billStatus":1,"paidStatus":1,"cbsStatus":1,"cbsReceipt":"EP…-130","billType":"BillPayment", …}] +``` + +Not used by Thijooree yet. + +--- + +  + +--- + +**Related:** [Number Lookup](01-number-lookup.md) · [Reload](02-reload.md) · +[BML Merchant Card Payment](../bmlapi/16-card-payment.md) · +App side: [Transfer Flows](../thijooree/20-transfer-flows.md#carrier-services-by-bml-card) + +[← Reload](02-reload.md) diff --git a/docs/dhiraaguapi/README.md b/docs/dhiraaguapi/README.md index 11ae713..0911488 100644 --- a/docs/dhiraaguapi/README.md +++ b/docs/dhiraaguapi/README.md @@ -97,6 +97,7 @@ The API only returns a valid result for numbers currently on the Dhiraagu networ |---|---|---| | 1 | [Number Lookup](01-number-lookup.md) | Validate a Dhiraagu number and determine account type | | 2 | [Reload](02-reload.md) | Easy TopUp order → BML merchant transaction, paid by card | +| 3 | [Bill Pay](03-bill-pay.md) | Easy Pay order → BML merchant transaction, paid by card | --- diff --git a/docs/thijooree/07-transfer.md b/docs/thijooree/07-transfer.md index d8ece92..34ea2c6 100644 --- a/docs/thijooree/07-transfer.md +++ b/docs/thijooree/07-transfer.md @@ -77,7 +77,7 @@ A phone number searched with no source yet (or from a BML card that can pay by c |---|---|---| | Favara Transfer | bank account behind the number | MIB or BML account | | Fahipay service | Raastas, Ooredoo Bill Pay, Dhiraagu Reload, Dhiraagu Bill Pay | Fahipay wallet | -| Card service | Dhiraagu Reload (BML badge) | Verified BML card | +| Card service | Dhiraagu Reload, Dhiraagu Bill Pay (BML badge) | Verified BML card | One option is applied straight away; with more, a picker opens and Send stays disabled until one is chosen. Picking a type also picks a source that can pay it. Fahipay and card services clear and disable the Remarks field and apply their own amount rules (minimum, maximum, whole amounts, 8% GST note). Details: [Transfer Flows → Transfer Type picker](20-transfer-flows.md#transfer-type-picker). @@ -122,11 +122,11 @@ When the source is a BML USD account and the destination is a MIB account but no See [Transfer Flows → Fahipay source](20-transfer-flows.md#fahipay-source). -### Carrier Service by BML Card (Dhiraagu Reload) +### Carrier Service by BML Card (Dhiraagu Reload / Bill Pay) -1. Checks the amount against Dhiraagu's rules (MVR 20–1000, whole amounts, 8% GST included) -2. A "Processing..." dialog shows while Dhiraagu creates the order and its BML merchant transaction ([Dhiraagu API → Reload](../dhiraaguapi/02-reload.md)) -3. From there it is the card-only merchant flow: the same confirm dialog and warning, biometric gate, card + 3-D Secure payment, and the return to Dhiraagu (`?wait=1`) that tops the number up +1. Checks the amount against Dhiraagu's rules (reload: MVR 20–1000, whole amounts, 8% GST included; bill pay: from MVR 1, up to 2 decimals, no GST) +2. A "Processing..." dialog shows while Dhiraagu creates the order and its BML merchant transaction ([Dhiraagu API → Reload](../dhiraaguapi/02-reload.md), [→ Bill Pay](../dhiraaguapi/03-bill-pay.md)) +3. From there it is the card-only merchant flow: the same confirm dialog and warning, biometric gate, card + 3-D Secure payment, and the return to Dhiraagu (`?wait=1`) that tops the number up or posts the bill payment 4. On success, the result shows inside the dialog; if Dhiraagu couldn't be notified, a toast gives the BML transaction id See [Transfer Flows → Carrier services by BML card](20-transfer-flows.md#carrier-services-by-bml-card). diff --git a/docs/thijooree/20-transfer-flows.md b/docs/thijooree/20-transfer-flows.md index d0636f6..a12167b 100644 --- a/docs/thijooree/20-transfer-flows.md +++ b/docs/thijooree/20-transfer-flows.md @@ -151,8 +151,8 @@ None of the Fahipay services take a reference. Picking one clears the Reference ### Carrier services by BML card A carrier service can also be paid with a verified BML card, through the carrier's own website -and its BML merchant gateway, instead of the Fahipay wallet. Only **Dhiraagu Reload** so far -(`CardPayoutService`, `ui/home/transfer/CardPayoutTransferHandler.kt`). +and its BML merchant gateway, instead of the Fahipay wallet. Only **Dhiraagu Reload** and +**Dhiraagu Bill Pay** so far (`CardPayoutService`, `ui/home/transfer/CardPayoutTransferHandler.kt`). **Which cards.** A card qualifies when it's verified and its BML login has an OTP seed, the same rule as card-only merchant links (`BmlVerifiedCards`, see @@ -165,6 +165,7 @@ drops the pick, like any other source that can't pay the picked type. | Carrier result | Service | |---|---| | Dhiraagu `RELOAD` | Dhiraagu Reload | +| Dhiraagu `BILL_PAY` | Dhiraagu Bill Pay | **Amount rules.** The carrier website's, not Fahipay's. They're checked the same way, through the shared `PayoutAmountField`: @@ -172,6 +173,9 @@ the shared `PayoutAmountField`: | Service | Min (MVR) | Max (MVR) | Decimals | GST | |---|---|---|---|---| | Dhiraagu Reload | 20 | 1,000 | no | 8%, included (credit = amount − round2(amount × 0.08 / 1.08)) | +| Dhiraagu Bill Pay | 1 | none | up to 2 places | none | + +Easy Pay itself sets no minimum or maximum; the MVR 1 floor is Thijooree's. **Reference.** None. The field is cleared and disabled, as for the Fahipay services. @@ -179,7 +183,9 @@ the shared `PayoutAmountField`: BML transaction comes from: 1. `CardPayoutTransferHandler.submit()` has the carrier create it for the number and amount - (`DhiraaguReloadClient.createBmlTransaction`, see [Dhiraagu API → Reload](../dhiraaguapi/02-reload.md)). + (`DhiraaguPaymentClient.createReloadTransaction` / `createBillPayTransaction`, see + [Dhiraagu API → Reload](../dhiraaguapi/02-reload.md) and [→ Bill Pay](../dhiraaguapi/03-bill-pay.md)). + Bill pay looks the number up again first, for the billing account the order is made out to. That takes a few round trips, so the payment's "Processing..." box shows meanwhile (`TransferFragment.showProcessingDialog`) and closes before the confirm dialog opens. 2. Its payment page is loaded (`BmlMerchantTxnClient.fetchPayPage`). If it doesn't take cards, or @@ -259,7 +265,7 @@ Source: Fahipay Transfer type: Card (verified BML card) └── Carrier creates a BML merchant transaction → card-only merchant flow - DHIRAAGU_RELOAD + DHIRAAGU_RELOAD, DHIRAAGU_BILL ``` --- diff --git a/docs/thijooree/29-card-verification-and-merchant-card-pay.md b/docs/thijooree/29-card-verification-and-merchant-card-pay.md index b02e49d..63c3734 100644 --- a/docs/thijooree/29-card-verification-and-merchant-card-pay.md +++ b/docs/thijooree/29-card-verification-and-merchant-card-pay.md @@ -9,7 +9,7 @@ Two linked features: whose merchant has **no BML Pay** is paid with a verified card via the Pomelo + 3-D Secure flow ([BML API → Merchant Card Payment](../bmlapi/16-card-payment.md)). The same flow pays [carrier services by BML card](20-transfer-flows.md#carrier-services-by-bml-card) (Dhiraagu - Reload), once the carrier has created the transaction. + Reload and Bill Pay), once the carrier has created the transaction. > ⚠️ The merchant card flow is scraped browser/ACS traffic, not a stable API. Storing the CVV is a > security/PCI liability. See the API doc's