ooredoo billpay via bml card

This commit is contained in:
2026-10-03 01:27:29 +05:00
parent 8795d5f758
commit bb76f4e591
9 changed files with 118 additions and 27 deletions
@@ -12,10 +12,10 @@ import java.util.Locale
import java.util.concurrent.TimeUnit
/**
* Ooredoo prepaid recharge (Raastas) through the ooredoo.mv Quick Pay page, paid by card on
* BML's merchant gateway. Ooredoo only creates the order; the money moves on the BML Merchant
* Services transaction it hands back, which is paid like any card-only BML merchant link. See
* `docs/ooredooapi/02-raastas.md`.
* Ooredoo prepaid recharge (Raastas) and bill payment through the ooredoo.mv Quick Pay pages,
* paid by card on BML's merchant gateway. Ooredoo only creates the order; the money moves on the
* BML Merchant Services transaction it hands back, which is paid like any card-only BML merchant
* link. See `docs/ooredooapi/02-raastas.md` and `docs/ooredooapi/03-bill-pay.md`.
*
* Every call blocks, so run it on an IO thread.
*/
@@ -32,15 +32,29 @@ class OoredooPaymentClient {
* that plus GST. Returns the 24-hex BML transaction id. Throws with Ooredoo's wording when
* the order is refused.
*/
fun createRaastasTransaction(number: String, amount: Int, charged: BigDecimal): String {
fun createRaastasTransaction(number: String, amount: Int, charged: BigDecimal): String =
createOrder(number, charged, amount.toString(), transType = "recharge", serviceType = "prepaid")
/**
* Creates the bill payment order for postpaid [number] (7 digits) and the BML transaction
* paying for it. [amount] is MVR, up to 2 decimal places; no GST. Returns the 24-hex BML
* transaction id. Throws with Ooredoo's wording when the order is refused.
*/
fun createBillPayTransaction(number: String, amount: BigDecimal): String =
createOrder(number, amount, money(amount), transType = "billpay", serviceType = "Mobile")
/** `POST PaymentGateway/bml` — one call makes the order and its BML transaction. */
private fun createOrder(
number: String, charged: BigDecimal, amountWithoutGst: String, transType: String, serviceType: String,
): String {
val msisdn = "960$number"
val body = JSONObject()
.put("msisdn", msisdn)
.put("purchaseAmount", String.format(Locale.US, "%.2f", charged.setScale(2, RoundingMode.HALF_UP)))
.put("amountWithoutGst", amount.toString())
.put("purchaseAmount", money(charged))
.put("amountWithoutGst", amountWithoutGst)
.put("receiverMsisdn", msisdn)
.put("transType", "recharge")
.put("serviceType", "prepaid")
.put("transType", transType)
.put("serviceType", serviceType)
.put("serviceTypeDisplayName", "Mobile")
val text = client.newCall(
Request.Builder().url("$BASE/ooredoo-prod/PaymentGateway/bml")
@@ -48,7 +62,6 @@ class OoredooPaymentClient {
.header("User-Agent", UA)
.header("Accept", "application/json")
.header("Origin", BASE)
.header("Referer", "$BASE/payment?source=Recharge&transType=2")
.build()
).execute().use { r ->
if (r.code in 500..599) throw BankServerException("Ooredoo")
@@ -58,13 +71,16 @@ class OoredooPaymentClient {
throw Exception("Unexpected response from Ooredoo")
}
if (obj.optString("status") != "OK" || obj.optString("code") != "2000") {
throw Exception(obj.optString("msg").ifBlank { "Ooredoo refused the recharge" })
throw Exception(obj.optString("msg").ifBlank { "Ooredoo refused the payment" })
}
val data = obj.optJSONObject("data") ?: throw Exception("Ooredoo didn't create the order")
return TXN_URL.find(data.optString("bmlUrl"))?.groupValues?.get(1)
?: throw Exception("BML didn't create the transaction")
}
private fun money(amount: BigDecimal) =
String.format(Locale.US, "%.2f", amount.setScale(2, RoundingMode.HALF_UP))
companion object {
private const val BASE = "https://www.ooredoo.mv"
private const val UA = "Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"
@@ -22,8 +22,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.
*
* No Ooredoo bill pay yet. Add it as a constant once its send path lands; the exhaustive `when`s
* over this enum point at every site that needs updating.
* Add new services as constants; the exhaustive `when`s over this enum point at every site that
* needs updating.
*/
enum class CardPayoutService(
override val label: String,
@@ -46,7 +46,9 @@ enum class CardPayoutService(
minAmount = 1, maxAmount = null, decimalsAllowed = true, gstPercent = null),
// Ooredoo credits the whole amount and charges the card 8% GST on top
OOREDOO_RAASTAS("Raastas", "Ooredoo · Raastas", R.drawable.ooredoo_logo,
minAmount = 20, maxAmount = null, decimalsAllowed = false, gstPercent = 8, gstAdded = true);
minAmount = 20, maxAmount = null, decimalsAllowed = false, gstPercent = 8, gstAdded = true),
OOREDOO_BILL("Ooredoo Bill Pay", "Ooredoo · Bill Pay", R.drawable.ooredoo_logo,
minAmount = 10, maxAmount = null, decimalsAllowed = true, gstPercent = null);
}
/**
@@ -95,6 +97,9 @@ class CardPayoutTransferHandler(
if (result.ooredoo == OoredooClient.CustType.PRE || result.ooredoo == OoredooClient.CustType.HYBRID) {
add(CardPayoutService.OOREDOO_RAASTAS)
}
if (result.ooredoo == OoredooClient.CustType.POST || result.ooredoo == OoredooClient.CustType.HYBRID) {
add(CardPayoutService.OOREDOO_BILL)
}
}.map { TransferType.Card(it, result.ownerName, cards) }
}
@@ -166,6 +171,8 @@ class CardPayoutTransferHandler(
DhiraaguPaymentClient().createBillPayTransaction(number, amount)
CardPayoutService.OOREDOO_RAASTAS ->
OoredooPaymentClient().createRaastasTransaction(number, amount.intValueExact(), charged)
CardPayoutService.OOREDOO_BILL ->
OoredooPaymentClient().createBillPayTransaction(number, amount)
}
BmlMerchantTxnClient().fetchPayPage(txnId)
}
+1 -1
View File
@@ -18,4 +18,4 @@
| [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 and bill pay by BML card |
| [ooredooapi/](ooredooapi/README.md) | Ooredoo Quick Pay — number validation, Raastas by BML card |
| [ooredooapi/](ooredooapi/README.md) | Ooredoo Quick Pay — number validation, Raastas and bill pay by BML card |
+1 -1
View File
@@ -104,4 +104,4 @@ cookie; okhttp from the phone gets through without one, as with
**Related:** [Number Validation](01-number-validation.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 Validation](01-number-validation.md)
[← Number Validation](01-number-validation.md) · [Bill Pay →](03-bill-pay.md)
+65
View File
@@ -0,0 +1,65 @@
# Bill Pay (Quick Pay, paid by BML card)
Pay an Ooredoo postpaid bill through the ooredoo.mv **Quick Pay** bill-pay page. It is the same
single order call as [Raastas](02-raastas.md) with a different `transType` / `serviceType`, and
no GST. From the order on, it's the BML card-only merchant flow and the same return to Ooredoo.
Reconstructed from `docs/ooredooapi/tmp/ooredoo_billpay_bml_card.har` (a Firefox HAR).
---
## Flow overview
```
GET /bill-pay
GET /ooredoo-prod/QuickPayPackage/v1/numberTypeValidation?… → custType POST (Number Validation)
POST /ooredoo-prod/PaymentGateway/bml → orderID, bmlUrl
── BML card-only merchant flow ── → TRANSACTION_CONFIRMED
GET transaction.merchants…/<id>?wait=1 → 302 my.ooredoo.mv/bml/response_new.php?…state=CONFIRMED
(200, auto-submits, transtype=1) → POST www.ooredoo.mv/ooredoo-prod/PaymentGateway/redirect/bml
→ /payment-status?statusDesc=sucess&status=1&order_id=<orderID>
```
The page doesn't look up the outstanding bill: the amount is whatever is typed.
---
## Order
`POST https://www.ooredoo.mv/ooredoo-prod/PaymentGateway/bml`, headers as in
[Raastas → Order](02-raastas.md#1-order).
```json
{"msisdn":"9609XXXXXX","purchaseAmount":"10.01","amountWithoutGst":"10.01",
"receiverMsisdn":"9609XXXXXX","transType":"billpay","serviceType":"Mobile",
"serviceTypeDisplayName":"Mobile"}
```
```json
{"status":"OK","msg":"Successfully generated order id","code":"2000",
"data":{"orderID":"36447962","purchaseAmount":"1001","shortUrl":"https://pay.bml.com.mv/…",
"bmlUrl":"https://transaction.merchants.bankofmaldives.com.mv/6ac011e099f9d890856e2d33", …}}
```
| | Raastas | Bill Pay |
|---|---|---|
| `transType` | `recharge` | `billpay` |
| `serviceType` | `prepaid` | `Mobile` |
| `amountWithoutGst` | amount credited | = `purchaseAmount` |
| GST | 8% added on top | none |
| Return form `transtype` | `2` | `1` |
### Limits
Minimum **MVR 10**, decimals allowed (up to 2 places). The maximum isn't known.
---
&nbsp;
---
**Related:** [Number Validation](01-number-validation.md) · [Raastas](02-raastas.md) ·
[BML Merchant Card Payment](../bmlapi/16-card-payment.md) ·
App side: [Transfer Flows](../thijooree/20-transfer-flows.md#carrier-services-by-bml-card)
[← Raastas](02-raastas.md)
+1
View File
@@ -82,6 +82,7 @@ The API expects the full MSISDN including country code `960` (e.g. `9609654321`)
|---|---|---|
| 1 | [Number Validation](01-number-validation.md) | Validate an Ooredoo number and determine account type |
| 2 | [Raastas](02-raastas.md) | Quick Pay recharge order → BML merchant transaction, paid by card |
| 3 | [Bill Pay](03-bill-pay.md) | Quick Pay bill payment order → BML merchant transaction, paid by card |
---
+4 -4
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, Dhiraagu Bill Pay, Raastas (BML badge) | Verified BML card |
| Card service | Dhiraagu Reload, Dhiraagu Bill Pay, Raastas, Ooredoo 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,10 +122,10 @@ 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 / Bill Pay, Ooredoo Raastas)
### Carrier Service by BML Card (Dhiraagu Reload / Bill Pay, Ooredoo Raastas / Bill Pay)
1. Checks the amount against the carrier's rules (Dhiraagu reload: MVR 20–1000, whole amounts, 8% GST included; Dhiraagu bill pay: from MVR 1, up to 2 decimals, no GST; Raastas: from MVR 20, whole amounts, 8% GST added on top)
2. A "Processing..." dialog shows while the carrier creates the order and its BML merchant transaction ([Dhiraagu API → Reload](../dhiraaguapi/02-reload.md), [→ Bill Pay](../dhiraaguapi/03-bill-pay.md), [Ooredoo API → Raastas](../ooredooapi/02-raastas.md))
1. Checks the amount against the carrier's rules (Dhiraagu reload: MVR 20–1000, whole amounts, 8% GST included; Dhiraagu bill pay: from MVR 1, up to 2 decimals, no GST; Raastas: from MVR 20, whole amounts, 8% GST added on top; Ooredoo bill pay: from MVR 10, up to 2 decimals, no GST)
2. A "Processing..." dialog shows while the carrier creates the order and its BML merchant transaction ([Dhiraagu API → Reload](../dhiraaguapi/02-reload.md), [→ Bill Pay](../dhiraaguapi/03-bill-pay.md), [Ooredoo API → Raastas](../ooredooapi/02-raastas.md), [→ Bill Pay](../ooredooapi/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 the carrier (`?wait=1`) that tops the number up or posts the bill payment. A decline (e.g. insufficient funds) or a rejected token code ends it with the bank's message
4. On success, the result shows inside the dialog; if Dhiraagu couldn't be notified, a toast gives the BML transaction id
+8 -6
View File
@@ -152,7 +152,7 @@ None of the Fahipay services take a reference. Picking one clears the Reference
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: **Dhiraagu Reload**, **Dhiraagu
Bill Pay** and **Ooredoo Raastas** so far (`CardPayoutService`, `ui/home/transfer/CardPayoutTransferHandler.kt`).
Bill Pay**, **Ooredoo Raastas** and **Ooredoo Bill Pay** (`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
@@ -167,6 +167,7 @@ drops the pick, like any other source that can't pay the picked type.
| Dhiraagu `RELOAD` | Dhiraagu Reload |
| Dhiraagu `BILL_PAY` | Dhiraagu Bill Pay |
| Ooredoo `PRE` or `HYBRID` | Raastas |
| Ooredoo `POST` or `HYBRID` | Ooredoo Bill Pay |
**Amount rules.** The carrier website's, not Fahipay's. They're checked the same way, through
the shared `PayoutAmountField`:
@@ -176,9 +177,10 @@ the shared `PayoutAmountField`:
| 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 |
| Raastas | 20 | none | no | 8%, **added** (charged = amount + round2(amount × 0.08)) |
| Ooredoo Bill Pay | 10 | none | up to 2 places | none |
Easy Pay itself sets no minimum or maximum; the MVR 1 floor is Thijooree's. Raastas' maximum
isn't known.
Easy Pay itself sets no minimum or maximum; the MVR 1 floor is Thijooree's. The Ooredoo maximums
aren't known.
Raastas by card is the one service where GST is added on top (`PayoutService.gstAdded`): the
number is credited what's typed, the card pays more, and the note under the amount says what's
@@ -192,9 +194,9 @@ BML transaction comes from:
1. `CardPayoutTransferHandler.submit()` has the carrier create it for the number and amount
(`DhiraaguPaymentClient.createReloadTransaction` / `createBillPayTransaction`,
`OoredooPaymentClient.createRaastasTransaction`, see
`OoredooPaymentClient.createRaastasTransaction` / `createBillPayTransaction`, see
[Dhiraagu API → Reload](../dhiraaguapi/02-reload.md), [→ Bill Pay](../dhiraaguapi/03-bill-pay.md)
and [Ooredoo API → Raastas](../ooredooapi/02-raastas.md)).
and [Ooredoo API → Raastas](../ooredooapi/02-raastas.md), [→ Bill Pay](../ooredooapi/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.
@@ -275,7 +277,7 @@ Source: Fahipay
Transfer type: Card (verified BML card)
└── Carrier creates a BML merchant transaction → card-only merchant flow
DHIRAAGU_RELOAD, DHIRAAGU_BILL, OOREDOO_RAASTAS
DHIRAAGU_RELOAD, DHIRAAGU_BILL, OOREDOO_RAASTAS, OOREDOO_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 and Bill Pay, Ooredoo Raastas), once the carrier has created the transaction.
Reload and Bill Pay, Ooredoo Raastas 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