update docs
Auto Tag on Version Change / check-version (push) Failing after 14m24s

This commit is contained in:
2026-06-27 18:35:38 +05:00
parent 86e1dc0521
commit 00e6b40ee0
3 changed files with 267 additions and 1 deletions
+1 -1
View File
@@ -279,4 +279,4 @@ python tmp/mfaisa_transfer.py <myMsisdn> <myMpin> <recipientMsisdn>
---
> **← Back to** [Transaction History](03-history.md) | [README](README.md)
> **← Back to** [Transaction History](03-history.md) | [README](README.md) | **Next →** [QR Merchant Payment](05-qr-pay.md)
+265
View File
@@ -0,0 +1,265 @@
# QR Merchant Payment ("Smart Pay")
Pay an Ooredoo M-Faisa merchant by scanning their QR. The QR encodes only a numeric `qrCodeId` (e.g. `1594103440350`) — no URL, no EMV TLV envelope. The flow is three calls and **does not require OTP** (`2FARequired=NONE`).
> **Currency / pocket constraints:** captures cover MVR→MVR purchases from the user's EMONEY pocket only. The `transactionCurrency` field is taken from the QR lookup; we have not seen a non-MVR variant.
---
## Flow
```
Client Server
| |
| POST /QRCodeUtility/fetchQRCodeById | ← user scanned a QR
| formData = { qrCodeId, tenantCode } |
|------------------------------------------------>|
| [{ success, response:[{ commercialName, |
| customerId, mobileNumber, currencyCode, |
| txnAmount, status, ... }] }] |
|<------------------------------------------------|
| |
| (show merchant, accept amount + remarks)
| |
| POST /initiateNewBuy |
| formData = { merchantId, mobileNumber, |
| sourceDetails, transactionAmount, |
| transactionType:"PURCHASE", … } |
|------------------------------------------------>|
| [{ 2FARequired:"NONE", |
| authenticationType:"NONE", |
| success:true, |
| response:[{ responseObject:{ referenceId, |
| chargeDetails, … } }] }] |
|<------------------------------------------------|
| |
| (no OTP — go straight to confirm)
| |
| POST /confirmNewBuy |
| formData = { referenceId } |
| transactionAuthDetails = "null" ← literal |
|------------------------------------------------>|
| [{ success:true, |
| message:"Payment Completed Successfully", |
| response:[{ responseObject:{ isCompleted, |
| balanceInquiryDTO, ... } }] }] |
|<------------------------------------------------|
```
All three endpoints carry the standard anti-replay pair (`rndValue` + `csValue`) derived from each request's `formData` JSON — see [01-encryption.md → rndValue / csValue](01-encryption.md#anti-replay-envelope-rndvalue--csvalue).
Unlike the transfer flow, **every endpoint here returns its envelope as a JSON array** `[{...}]` for both success and error.
---
## Step 1: `QRCodeUtility/fetchQRCodeById` — resolve merchant
### Endpoint
```
POST https://superapp.ooredoo.mv/api/mfaisaa-bff/mfino/v1.1/web/QRCodeUtility/fetchQRCodeById
```
### Request
**Content-Type:** `application/x-www-form-urlencoded`
| Field | Value |
|---|---|
| `role` | **`R01`** (the other two endpoints use `RETAIL_SUBSCRIBER` — this one does not) |
| `channel` | `C03` |
| `loginExchangeKey` | From login |
| `rndValue` / `csValue` | [Standard anti-replay](01-encryption.md#anti-replay-envelope-rndvalue--csvalue) |
| `formData` | JSON below ([html-safe `=` escaping](01-encryption.md#html-safe-gson--escape)) |
```json
{
"qrCodeId": "<numeric id from QR>",
"tenantCode": "ooredoo"
}
```
### Response — happy path
```json
[
{
"success": true,
"message": "QRCode fetched Successfully.",
"response": [
{
"mobileNumber": "9609569506", /* merchant's '960' + msisdn */
"customerId": "72518", /* used as merchantId in step 2 */
"commercialName": "Family Room", /* merchant display name */
"qrCodeId": "1594103440350",
"qrImageString": "<base64 PNG>", /* unused */
"accountNumber": null,
"txnAmount": null, /* static QR; dynamic QRs put a number here */
"currencyCode": "MVR",
"status": "Active",
"role": "AGENT",
"tenantCode": "ooredoo",
"...": "..."
}
]
}
]
```
`accountNumber` / `txnAmount` are JSON `null` for **static QRs** (the user chooses the amount). For **dynamic QRs** the server returns the fixed amount in `txnAmount` and the client should lock the amount field.
The `mobileNumber` field already includes the `960` country prefix.
### Response — QR not found / inactive
```json
[{ "success": false, "message": "QRCode not found." }]
```
The client also rejects entries with `status != "Active"`.
---
## Step 2: `initiateNewBuy` — initiate purchase (no OTP triggered)
### Endpoint
```
POST https://superapp.ooredoo.mv/api/mfaisaa-bff/mfino/v1.1/web/initiateNewBuy
```
### Request
**Content-Type:** `application/x-www-form-urlencoded`
| Field | Value |
|---|---|
| `role` | `RETAIL_SUBSCRIBER` |
| `channel` | `C03` (top-level differs from inner `formData.channel`, which is `SubscriberApp`) |
| `loginExchangeKey` | From login |
| `rndValue` / `csValue` | Standard anti-replay (derived from the `formData` below) |
| `formData` | JSON below |
```json
{
"channel": "SubscriberApp",
"commodityType": "WALLET",
"description": "<remarks>", /* free text, may be empty */
"merchantId": "<customerId from step 1>",
"mobileNumber": "<merchant '960' msisdn from step 1>",
"sourceDetails": {
"MDNId": "960<myMsisdn>", /* PLAINTEXT '960' + my phone */
"actorRoleType": "RETAIL_SUBSCRIBER",
"pocketId": "<my source pocket id>" /* EMONEY pocket from login */
},
"transactionAmount": "<amount>", /* string, e.g. "7.56" */
"transactionCurrency": "MVR",
"transactionType": "PURCHASE"
}
```
Unlike the transfer flow's `initiateFTRequest`, this endpoint does **not** take an `identifier` header field or `tPin`.
### Response — happy path
```json
[
{
"2FARequired": "NONE", /* the key difference */
"authenticationType": "NONE",
"success": true,
"message": "Purchase Initiated Successfully",
"response": [
{
"requestObject": { "...": "..." },
"responseObject": {
"referenceId": "685011023630",
"transactionAmount": { "amount": 7.56, "currencyCode": "MVR" },
"netAmount": { "amount": 7.56, "currencyCode": "MVR" },
"chargeDetailsDTO": { "totalFeesInTenantCurrency": { "amount": 0.0, "...": "..." }, "...": "..." },
"isCompleted": false,
"authenticationType":"NONE",
"...": "..."
}
}
]
}
]
```
Cache `referenceId` for step 3. No SMS is sent — proceed straight to confirm.
> **Defensive check:** the Thijooree client throws if it ever sees `2FARequired != "NONE"` on this endpoint so a no-op confirm can't silently complete. If Ooredoo ever turns 2FA on for QR pay, you'll see a clear error instead of a partial transaction.
---
## Step 3: `confirmNewBuy` — settle purchase
### Endpoint
```
POST https://superapp.ooredoo.mv/api/mfaisaa-bff/mfino/v1.1/web/confirmNewBuy
```
### Request
**Content-Type:** `application/x-www-form-urlencoded`
| Field | Value |
|---|---|
| `role` | `RETAIL_SUBSCRIBER` |
| `channel` | `C03` |
| `loginExchangeKey` | From login |
| `rndValue` / `csValue` | Anti-replay derived from `formData` below |
| `formData` | `{"referenceId": "<from step 2>"}` |
| `transactionAuthDetails` | **literal string `"null"`** (not a JSON null, not an empty string — the captured request sends the four-character string `null`) |
### Response — happy path
```json
[
{
"success": true,
"message": "Payment Completed Successfully",
"response": [
{
"responseObject": {
"isCompleted": true,
"balanceInquiryDTO": {
"currencyCode": "MVR",
"pocketAmount": 92.85,
"pocketId": "<source pocket id>",
"pocketBalanceMap": { "...": "..." }
},
"status": { "replyCode": 0.0 },
"...": "..."
}
}
]
}
]
```
### Session expiry
Same envelope as elsewhere — `attributeValue: "SESSION_EXPIRED"` with HTTP 200; the client throws `MfaisaSessionExpiredException`. See [03-history.md → Session expiry](03-history.md#session-expiry).
---
## Optional: `save/smart-pay-recipient` — bookkeeping
After a successful confirm the official Ooredoo app saves the merchant to a server-side "recent recipients" list:
```
POST https://superapp.ooredoo.mv/api/mfaisaa-bff/save/smart-pay-recipient
```
This is **not** required for the payment itself — the transfer is final once `confirmNewBuy` succeeds. Thijooree skips it; the merchant is kept in the local picker `RecentsCache` under an `mfaisaqr:<qrCodeId>` synthetic accountNumber (mirroring the BML QR `bmlqr:` scheme).
---
&nbsp;
---
> **← Back to** [Transfer Money](04-transfer.md) | [README](README.md)
+1
View File
@@ -88,6 +88,7 @@ Client Server
| 2 | [Login](02-login.md) | Subscriber lookup + mPIN login |
| 3 | [Transaction History](03-history.md) | Paginated history per session |
| 4 | [Transfer Money](04-transfer.md) | Three-step wallet-to-wallet send: recipient lookup → initiate (server SMSes OTP) → confirm |
| 5 | [QR Merchant Payment](05-qr-pay.md) | Three-step "smart pay" scan-to-merchant: QR lookup → initiate → confirm. **No OTP** (`2FARequired=NONE`) |
---