ooredoo raastas via bml card
Auto Tag on Version Change / check-version (push) Successful in 6s

This commit is contained in:
2026-10-03 01:08:33 +05:00
parent 0d4f0074c7
commit 8795d5f758
16 changed files with 370 additions and 42 deletions
+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 for Raastas / bill pay |
| [ooredooapi/](ooredooapi/README.md) | Ooredoo Quick Pay — number validation, Raastas by BML card |
+30 -3
View File
@@ -205,8 +205,10 @@ no `action`; their JavaScript posts back to the **same creq URL**. So the creq U
`destValue=token`, `selectChannel=token`, `authMethod=OOB`, `otpDest=`, `formReqType=SUBMIT`
(keep the hidden `creq` / `otpChannels`).
3. **POST channel** → the **OTP entry** page (`otpValue` input). Submit `otpValue=<BML token TOTP>`,
`formReqType=SUBMIT`. A wrong/expired code re-renders the OTP page with text containing
*"incorrect"* / *"expired"* — regenerate the TOTP and retry once.
`formReqType=SUBMIT`. A wrong/expired code re-renders the OTP page (still with `otpValue`) and
*"The OTP code you entered is incorrect Please try again."* — wait for the next TOTP window,
regenerate and retry once. Rejected twice, the payment stops ("The bank rejected the BML token
code"). The app also never sends a code with under 5 s left in its window.
4. On success the ACS returns a form auto-posting **`cres`** to the Mastercard gateway; the gateway
returns a form auto-posting the result (`order.id`, `result=SUCCESS`, …) to
**`transactions/mpgsNotification/<id>`**. Follow both so the verdict is recorded.
@@ -225,6 +227,23 @@ Poll `next-action` until the recorded verdict surfaces:
| `TRANSACTION_CONFIRMED` | Success |
| `TRANSACTION_FAILED` | Declined |
**A decline after 3-D Secure doesn't arrive this way.** In the Ooredoo capture, the card passed
3-D Secure (`mpgsNotification` got `result=SUCCESS`, `gatewayRecommendation=PROCEED`) and was then
declined for insufficient funds. The browser's `?wait=1` went to `?error=1` instead of the
merchant, and the transaction stayed payable: `state` still `QR_CODE_GENERATED`, `hasError: true`,
`allowRetry: true`, and a new `paymentErrorHistory` entry:
```json
{"date":"…","vendor":"mpgs","code":"INSUFFICIENT_FUNDS",
"reason":"Transaction declined due to insufficient funds",
"customerVisibleDescription":"Insufficient funds. Please use another card or payment method."}
```
The same link was then paid successfully after topping up the card. So while polling,
`BmlMerchantCardPayClient` also reads the transaction (the load PATCH,
`BmlMerchantTxnClient.paymentErrors`) and stops with `customerVisibleDescription` as soon as an
entry newer than the ones there before the attempt shows up.
## 7. Return to the merchant
The `mpgsNotification` response is a page whose script sends the browser to
@@ -240,13 +259,21 @@ GET transaction…/<id>?wait=1
(FahiPay's is `fahipay.mv/api/bml/gateway/callback/?…state=CONFIRMED`.)
Ooredoo's callback isn't a redirect: `my.ooredoo.mv/bml/response_new.php?…state=CONFIRMED` is a
200 page whose `<body onload="document.forms['wtmpay'].submit()">` posts the result
(`order_id`, `bml_transaction_id`, `bml_response=CONFIRMED`, `payment_status=success`, …) on to
`www.ooredoo.mv/ooredoo-prod/PaymentGateway/redirect/bml`, which lands on
`/payment-status?order_id=…&status=1`. `returnToMerchant` submits such auto-posting forms too
(up to 2).
**This hop is required.** It's how at least Dhiraagu learns it was paid: a test reload that
stopped at `TRANSACTION_CONFIRMED` charged the card but never topped up, and opening the
`?wait=1` URL in a browser afterwards delivered it. The signature is generated by BML, so the
hop can be replayed later from the transaction id alone.
`BmlMerchantCardPayClient` does it after every confirmed payment (`returnToMerchant`): a browser
UA GET that follows the redirects, up to 3 tries, success = the chain ends on a 2xx page. The
UA GET that follows the redirects and auto-submitted forms, up to 3 tries, success = the chain
ends on a 2xx page. The
merchant host may be behind Cloudflare: plain `curl` got a 403 on `dhiraagu.com.mv`, okhttp got
through.
+1 -1
View File
@@ -129,4 +129,4 @@ Always fall back to the other provider's lookup if this API returns `custType: n
---
[← README](README.md)
[← README](README.md) · [Raastas →](02-raastas.md)
+107
View File
@@ -0,0 +1,107 @@
# Raastas (Quick Pay recharge, paid by BML card)
Recharge an Ooredoo prepaid number through the ooredoo.mv **Quick Pay** page. Ooredoo creates the
order and hands back a **BML Merchant Services transaction**, which is paid like any card-only
BML merchant link ([BML API → Merchant Card Payment](../bmlapi/16-card-payment.md)).
Reconstructed from `docs/ooredooapi/tmp/ooredoo_raastas_bml_card.har` (a Firefox HAR, which also
holds a rejected OTP and an insufficient-funds decline on the same transaction).
---
## Flow overview
```
GET /ooredoo-prod/QuickPayPackage/v1/numberTypeValidation?… → custType PRE (Number Validation)
POST /ooredoo-prod/PaymentGateway/bml → orderID, bmlUrl ──┐
│
── from here: the BML card-only merchant flow ── │
GET transaction.merchants…/<id> ←──────────────────────────────────────────────────┘
… Pomelo tokenise, next-action, Wibmo 3-D Secure, MPGS … → TRANSACTION_CONFIRMED
GET transaction.merchants…/<id>?wait=1 → 302 my.ooredoo.mv/bml/response_new.php?…state=CONFIRMED
(200, auto-submits) → POST www.ooredoo.mv/ooredoo-prod/PaymentGateway/redirect/bml
→ /payment-status?order_id=<orderID>&statusDesc=sucess&status=1
```
Unlike Dhiraagu, there's no nonce or cart: one POST makes the order and the BML transaction.
---
## 1. Order
`POST https://www.ooredoo.mv/ooredoo-prod/PaymentGateway/bml`
| Header | Value |
|---|---|
| `Content-Type` | `application/json` |
| `Accept` | `application/json` |
| `Origin` | `https://www.ooredoo.mv` |
```json
{"msisdn":"9609XXXXXX","purchaseAmount":"21.60","amountWithoutGst":"20",
"receiverMsisdn":"9609XXXXXX","transType":"recharge","serviceType":"prepaid",
"serviceTypeDisplayName":"Mobile"}
```
```json
{"status":"OK","msg":"Successfully generated order id","code":"2000",
"data":{"orderID":"36447930","purchaseAmount":"2160","hashSignature":"…",
"shortUrl":"https://pay.bml.com.mv/7A86qLZ1QV",
"bmlUrl":"https://transaction.merchants.bankofmaldives.com.mv/6ac009f7bd9b264b80ba6abf", …}}
```
The 24-hex id at the end of `bmlUrl` is the transaction (`shortUrl` 301s to the same page). The
page is card-only, no BML Pay. The transaction's `localId` is the MSISDN, `customerReference` the
order id, and it expires after 7 days.
### GST
**Added on top**, not taken out: the number is credited `amountWithoutGst` and the card pays
`purchaseAmount = amount + toFixed2(amount × 8 / 100)` (MVR 20 → 21.60). The page's payment
method list gives `gstPercent: 8` for BML. This is the opposite of Raastas through Fahipay, where
GST comes out of the amount.
### Limits
Whole MVR, minimum **20** (before GST, so the card pays at least 21.60). The maximum isn't known;
the capture starts on the payment page.
---
## 2. Return to Ooredoo
`?wait=1` 302s to `https://my.ooredoo.mv/bml/response_new.php?transactionId=<id>&state=CONFIRMED&signature=<hash>`.
That page is a 200 that auto-submits:
```html
<body onload="document.forms['wtmpay'].submit()">
<form method="POST" action="https://www.ooredoo.mv/ooredoo-prod/PaymentGateway/redirect/bml" name="wtmpay">
order_id=36447930 amount=21.60000000 msisdn=9609XXXXXX transtype=2
bml_transaction_id=<id> bml_hash=<hash> bml_response=CONFIRMED
error_code=0 payment_status=success ptype=bml
```
which ends on `https://www.ooredoo.mv/payment-status?order_id=36447930&statusDesc=sucess&status=1`
(the receipt: `totalAmount 21.6`, `amountWithoutGst 20`, `gstAmt 1.6`). The card flow submits the
form, see [BML API → Return to the merchant](../bmlapi/16-card-payment.md#7-return-to-the-merchant).
A declined attempt sends `?wait=1` to `transaction…/<id>?error=1` instead, and the same
transaction can be paid again.
---
## Cloudflare
`ooredoo.mv` and `my.ooredoo.mv` are behind Cloudflare. The capture carries a `cf_clearance`
cookie; okhttp from the phone gets through without one, as with
[Number Validation](01-number-validation.md).
---
&nbsp;
---
**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)
+1
View File
@@ -81,6 +81,7 @@ The API expects the full MSISDN including country code `960` (e.g. `9609654321`)
| # | File | Description |
|---|---|---|
| 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 |
---
+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, Dhiraagu Bill Pay (BML badge) | Verified BML card |
| Card service | Dhiraagu Reload, Dhiraagu Bill Pay, Raastas (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 / Bill Pay)
### Carrier Service by BML Card (Dhiraagu Reload / Bill Pay, Ooredoo Raastas)
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
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))
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
See [Transfer Flows → Carrier services by BML card](20-transfer-flows.md#carrier-services-by-bml-card).
+16 -6
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** and
**Dhiraagu Bill Pay** so far (`CardPayoutService`, `ui/home/transfer/CardPayoutTransferHandler.kt`).
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`).
**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
@@ -166,6 +166,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 |
**Amount rules.** The carrier website's, not Fahipay's. They're checked the same way, through
the shared `PayoutAmountField`:
@@ -174,8 +175,15 @@ 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)) |
Easy Pay itself sets no minimum or maximum; the MVR 1 floor is Thijooree's.
Easy Pay itself sets no minimum or maximum; the MVR 1 floor is Thijooree's. Raastas' maximum
isn'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
paid ("You pay MVR 21.60 with 8% GST"). The order check in step 2 below compares against
`chargedWithGst`.
**Reference.** None. The field is cleared and disabled, as for the Fahipay services.
@@ -183,8 +191,10 @@ Easy Pay itself sets no minimum or maximum; the MVR 1 floor is Thijooree's.
BML transaction comes from:
1. `CardPayoutTransferHandler.submit()` has the carrier create it for the number and amount
(`DhiraaguPaymentClient.createReloadTransaction` / `createBillPayTransaction`, see
[Dhiraagu API → Reload](../dhiraaguapi/02-reload.md) and [→ Bill Pay](../dhiraaguapi/03-bill-pay.md)).
(`DhiraaguPaymentClient.createReloadTransaction` / `createBillPayTransaction`,
`OoredooPaymentClient.createRaastasTransaction`, see
[Dhiraagu API → Reload](../dhiraaguapi/02-reload.md), [→ Bill Pay](../dhiraaguapi/03-bill-pay.md)
and [Ooredoo API → Raastas](../ooredooapi/02-raastas.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.
@@ -265,7 +275,7 @@ Source: Fahipay
Transfer type: Card (verified BML card)
└── Carrier creates a BML merchant transaction → card-only merchant flow
DHIRAAGU_RELOAD, DHIRAAGU_BILL
DHIRAAGU_RELOAD, DHIRAAGU_BILL, OOREDOO_RAASTAS
```
---
@@ -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), once the carrier has created the transaction.
Reload and Bill Pay, Ooredoo Raastas), 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