add dhiraagu bill pay

This commit is contained in:
2026-10-03 00:26:00 +05:00
parent a0c515103a
commit 237d25af67
10 changed files with 251 additions and 37 deletions
@@ -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<String> {
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 {
@@ -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<String>): List<TransferType.Card> {
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)
}
+1 -1
View File
@@ -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 |
+1 -1
View File
@@ -235,7 +235,7 @@ merchant's own receipt page:
```
GET transaction…/<id>?wait=1
→ 302 https://www.dhiraagu.com.mv/api/dhiraagu-bml-response.aspx?transactionId=<id>&state=CONFIRMED&signature=<sha1>
→ 302 https://www.dhiraagu.com.mv/services/reload-receipt?pyid=<paymentId>
→ 302 https://www.dhiraagu.com.mv/services/reload-receipt?pyid=<paymentId> (bill pay: /services/bill-receipt)
```
(FahiPay's is `fahipay.mv/api/bml/gateway/callback/?…state=CONFIRMED`.)
+1 -1
View File
@@ -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)
+132
View File
@@ -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=<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…/<id>?wait=1 → 302 dhiraagu-bml-response.aspx (posts the payment)
→ 302 /services/bill-receipt?pyid=<paymentId>
```
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":"<BML txn id>","accountNumber":"1466154","serviceNumber":"7XXXXXX",
"amount":1.05,"billStatus":1,"paidStatus":1,"cbsStatus":1,"cbsReceipt":"EP…-130","billType":"BillPayment", …}]
```
Not used by Thijooree yet.
---
&nbsp;
---
**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)
+1
View File
@@ -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 |
---
+5 -5
View File
@@ -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).
+10 -4
View File
@@ -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
```
---
@@ -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