forked from shihaam/thijooree
ooredoo mfaisa
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# Transaction History
|
||||
|
||||
Fetch a paginated list of transactions for the active subscriber session.
|
||||
|
||||
---
|
||||
|
||||
## Endpoint
|
||||
|
||||
```
|
||||
POST https://superapp.ooredoo.mv/api/mfaisaa-bff/mfino/v1.1/web/transactionInquiry/fetchSummary
|
||||
```
|
||||
|
||||
Requires an active session (i.e. a valid [`loginExchangeKey`](02-login.md#step-2-domobilelogin)) obtained from `doMobileLogin`. Sessions expire after `mobileLoginSessionTimeout` seconds (240s) — see [Session expiry](#session-expiry) below.
|
||||
|
||||
---
|
||||
|
||||
## Request
|
||||
|
||||
**Content-Type:** `application/x-www-form-urlencoded`
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| `role` | `RETAIL_SUBSCRIBER` (constant) |
|
||||
| `channel` | `SubscriberApp` — **not** `C03` as in login |
|
||||
| `loginExchangeKey` | From the login response |
|
||||
| `mdnId` | `<encryptMobile(msisdn), base64>` — same routine as `mdnId` in login |
|
||||
| `formData` | JSON object (see below), [html-safe-escaped](01-encryption.md#html-safe-gson--escape) |
|
||||
| `rndValue` | [Anti-replay nonce](01-encryption.md#anti-replay-envelope-rndvalue--csvalue) |
|
||||
| `csValue` | [Adler32 integrity check](01-encryption.md#anti-replay-envelope-rndvalue--csvalue) |
|
||||
|
||||
### `formData` JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"actorRole": "RETAIL_SUBSCRIBER",
|
||||
"actorRoleId": "<suscriberId from login response>",
|
||||
"fromDate": "",
|
||||
"mdnId": "<encryptMobile(msisdn), base64>",
|
||||
"pageNo": "1",
|
||||
"recordSize": "70",
|
||||
"toDate": "",
|
||||
"transactionType": ""
|
||||
}
|
||||
```
|
||||
|
||||
- `actorRoleId` comes from `suscriberId` at the top level of `doMobileLogin`'s success body — see [02-login.md](02-login.md#step-2-domobilelogin). It also appears as `pocketDetails[0].roleId` in the same response.
|
||||
- The inner `mdnId` is independently encrypted from the outer `mdnId` — same plaintext, different ciphertext (OAEP random padding).
|
||||
- `fromDate` / `toDate` are empty strings in the official app — the server returns all available history.
|
||||
|
||||
---
|
||||
|
||||
## curl Example
|
||||
|
||||
```bash
|
||||
# Requires a valid loginExchangeKey + suscriberId from a fresh login call
|
||||
python tmp/mfaisa_history.py <msisdn> <mpin> 1
|
||||
```
|
||||
|
||||
See `tmp/mfaisa_history.py` for the full Python reference (it does the login, captures the session, then calls fetchSummary).
|
||||
|
||||
---
|
||||
|
||||
## Response
|
||||
|
||||
```json
|
||||
{
|
||||
"transactionInquiryDTOList": [
|
||||
{
|
||||
"requestId": "<request id>",
|
||||
"referenceId": "<reference id>",
|
||||
"sourceMDN": "<Subscriber Name>-DT Pocket-<msisdn>",
|
||||
"sourcePocketId":"<pocket id>",
|
||||
"actorRoleType": "RETAIL_SUBSCRIBER",
|
||||
"actorRoleId": "<subscriber id>",
|
||||
"commodityType": "WALLET",
|
||||
"channel": "SubscriberApp",
|
||||
"transactionAmount": { "amount": 1, "currencyCode": "MVR" },
|
||||
"userStatus": "CONFIRMED",
|
||||
"trnStage": "AUTO_REVERSED",
|
||||
"trnType": "CASH_IN",
|
||||
"status": "FAILED",
|
||||
"trnDate": "2026-06-13 13:04:19",
|
||||
"narrationString": "Load Money",
|
||||
"typeSummaryString": "[{\"Transaction Type\":\"Load Money\",\"Deposit Pocket\":\"DT Pocket\",\"Reference\":\"<ref>\"}]",
|
||||
"errorCode": "INT_002",
|
||||
"errorDesc": "QR_CODE_GENERATED",
|
||||
"resolutionDetails": "Please reconcile with the payment gateway..."
|
||||
},
|
||||
"..."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Key fields used by Thijooree:
|
||||
|
||||
| Field | Maps to `BankTransaction` |
|
||||
|---|---|
|
||||
| `trnDate` | `date` (already in `YYYY-MM-DD HH:mm:ss` form) |
|
||||
| `narrationString` | `description` (suffixed with `· Failed` when `status == "FAILED"`) |
|
||||
| `transactionAmount.amount` | `amount` (signed — see direction rule below) |
|
||||
| `transactionAmount.currencyCode` | `currency` |
|
||||
| `referenceId` (fallback `requestId`) | `id` + `reference` |
|
||||
| `typeSummaryString` → `Merchant Name` / `Receiver Name` / `Sender Name` | `counterpartyName` |
|
||||
| `sourceMDN` (e.g. `"<Name>-DT Pocket-<msisdn>"`) | `counterpartyName` fallback (first segment before `-`) |
|
||||
|
||||
### Debit / credit direction
|
||||
|
||||
The response does not include a signed amount or direction flag. Direction is inferred from `trnType`:
|
||||
|
||||
| `trnType` | Direction |
|
||||
|---|---|
|
||||
| `CASH_IN`, `RECEIVE_MONEY`, `*_IN` | credit (positive amount) |
|
||||
| everything else (`PURCHASE`, `TRANSFER`, …) | debit (negative amount) |
|
||||
|
||||
### Pagination
|
||||
|
||||
The server does not return a `total` field. Thijooree treats "received a full `recordSize` (= 70) records" as the only signal that further pages may exist; the next call uses `pageNo = pageNo + 1`. Once a page comes back with fewer than 70 records, no more pages are fetched.
|
||||
|
||||
### Session expiry
|
||||
|
||||
When the 240-second session lapses, the server still returns HTTP 200 but the body is its standard error envelope:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"success": false,
|
||||
"message": "validation errors",
|
||||
"error": [
|
||||
{
|
||||
"objectName": "LoginLog",
|
||||
"attributeName": "LoginLog",
|
||||
"attributeValue": "SESSION_EXPIRED",
|
||||
"errorCode": "SESSION_EXPIRED",
|
||||
"errorMessage": "SESSION_EXPIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`MfaisaHistoryClient` parses this into `MfaisaSessionExpiredException`. Callers (`HistoryFetcher`, `TransferHistoryFragment`) catch it, call `BasedBankApp.refreshMfaisaSession(loginId)` to re-login transparently, and retry the same page once.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
> **Next →** [Transfer Money](04-transfer.md) | **← Back to** [Login](02-login.md) | [README](README.md)
|
||||
Reference in New Issue
Block a user