update mib api docs
Auto Tag on Version Change / check-version (push) Successful in 6s

This commit is contained in:
2026-10-23 05:38:33 +05:00
parent 740030e061
commit 2764c6e452
10 changed files with 662 additions and 658 deletions
+38
View File
@@ -44,6 +44,44 @@ To switch between profiles (personal ↔ business), call `P47` again with the ot
---
## Refresh Accounts — `sfunc=n`, `routePath: A80`
**Key**: session key
Re-fetches the account list and balances for the **currently selected** profile without re-selecting it. No route-specific fields.
**Request**:
```json
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random 20-bit int>",
"routePath": "A80",
"xxid": "<session xxid>"
}
}
```
**Response**:
```json
{
"success": true,
"responseCode": "1",
"reasonCode": "101",
"reasonText": "Accounts retrieval success!",
"data": [ ... ],
"beneficiaryVersion": 0,
"menuVersion": "<hash>"
}
```
`data` has the same shape as the `accountBalance` array below. Some responses carry `responseCode: "3"`, `reasonCode: "90"`, `reasonText: "unknown error"` with `success: true` and a populated `data` array — treat `success` as authoritative.
---
## Profiles (from `A41` response)
The `A41` login init response includes `operatingProfiles`:
+74 -50
View File
@@ -1,41 +1,46 @@
# Transaction History
Fetch paginated transaction history for a single MIB account. Served from the WebView subdomain.
Fetch paginated transaction history for a single MIB account. Served from the encrypted API.
The account must belong to the **currently selected** profile — call `P47` for its profile first (see [03-accounts.md](03-accounts.md)).
---
## Endpoint
## Transaction History — `sfunc=n`, `routePath: A84`
**Key**: session key
**Request**:
```json
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"accountNo": "90101000000001000",
"start": 0,
"end": 19,
"includeCount": true,
"trxType": 0,
"sortTrx": "date",
"sortDir": "desc",
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random 20-bit int>",
"routePath": "A84",
"xxid": "<session xxid>"
}
}
```
POST https://faisamobilex-wv.mib.com.mv/ajaxAccounts/trxHistory
```
---
## Authentication
See [README](README.md) for cookie and AJAX header format.
```
Referer: https://faisamobilex-wv.mib.com.mv//accountDetails?trxh=1&dashurl=1&accountNo=<accountNo>
```
---
## Request Body (form-urlencoded)
| Field | Example | Description |
|---|---|---|
| `accountNo` | `90101000000001000` | Account number to fetch history for |
| `trxNo` | `` | Transaction number filter (empty = all) |
| `start` | `0` | First row (0-based, inclusive) |
| `end` | `19` | Last row (inclusive) |
| `includeCount` | `true` | Include `total_count` in response |
| `trxType` | `0` | Transaction type filter (`0` = all) |
| `sortTrx` | `date` | Sort field |
| `sortDir` | `desc` | Sort direction |
| `fromDate` | `` | From date filter (empty = no filter) |
| `toDate` | `` | To date filter (empty = no filter) |
| `start` | `1` | Start record index (1-based) |
| `end` | `20` | End record index (`start + pageSize - 1`) |
| `includeCount` | `1` | Include `total_count` in response |
### Pagination
@@ -43,9 +48,9 @@ Page size is 20. Compute `start`/`end` per page:
| Page | `start` | `end` |
|---|---|---|
| 1 | `1` | `20` |
| 2 | `21` | `40` |
| N | `(N-1)*20 + 1` | `N*20` |
| 1 | `0` | `19` |
| 2 | `20` | `39` |
| N | `(N-1)*20` | `N*20 - 1` |
Stop when total fetched equals `total_count` or `data` is empty.
@@ -56,25 +61,36 @@ Stop when total fetched equals `total_count` or `data` is empty.
```json
{
"success": true,
"responseCode": "1",
"reasonCode": "103",
"reasonText": "Trx History Retrieval Success!",
"total_count": "87",
"pos": "0",
"data": [
{
"trxNumber": "TXN20260516001",
"trxDate": "2026-05-16",
"descr1": "Transfer Debit",
"baseAmount": "-500.00",
"accountNo": "90101000000001000",
"curCode": "462",
"curCodeDesc": "MVR",
"benefName": "Mohamed Ali",
"trxNumber2": "FT20260516001"
},
{
"trxNumber": "TXN20260515001",
"trxDate": "2026-05-15",
"descr1": "Transfer Credit",
"baseAmount": "1000.00",
"curCodeDesc": "MVR",
"benefName": "",
"trxNumber2": ""
"trxNumber": "1-158043140-93434633-1",
"trxNumber2": "93434633",
"trxDate": "2026-05-16 15:10:25",
"trxValDate": "2026-05-16 00:00:00",
"absAmount": "500",
"baseAmount": "-500",
"foreignAmount": "-500",
"descr1": "Favara Transfer Web",
"descr2": "<reference>",
"descr3": "<details>",
"remarks": "<details>",
"trxType": "803",
"AddDate": "2026-05-16 15:10:25",
"bankCode": "1",
"bankName": "Maldives Islamic Bank",
"benefName": "BML - MOHAMED ALI",
"otherAccountNo": "-",
"fromAcc": null,
"imageHash": null,
"bankColor": "#FE860E"
}
]
}
@@ -84,19 +100,27 @@ Stop when total fetched equals `total_count` or `data` is empty.
|---|---|---|
| `success` | `bool` | `true` on success |
| `total_count` | `string` | Total transaction count — parse to `int` |
| `pos` | `string` | Echo of the requested start row |
| `data` | `array` | Transactions for this page |
### Transaction Object
| Field | Type | Description |
| Field | Consumed | Description |
|---|---|---|
| `trxNumber` | `string` | Unique transaction ID |
| `trxDate` | `string` | Transaction date (`YYYY-MM-DD`) |
| `descr1` | `string` | Transaction description — trim whitespace |
| `baseAmount` | `string` | Decimal string — **negative = debit, positive = credit** |
| `curCodeDesc` | `string` | Currency code (e.g. `"MVR"`, `"USD"`) |
| `benefName` | `string` | Counterparty name — blank or literal `"null"` means none |
| `trxNumber2` | `string` | Secondary reference; may be blank |
| `trxNumber` | yes | Unique transaction ID |
| `trxDate` | yes | Transaction timestamp (`yyyy-MM-dd HH:mm:ss`) |
| `descr1` | yes | Transaction description — trim whitespace |
| `foreignAmount` | yes | Amount in the **account's** currency — **negative = debit, positive = credit** |
| `curCodeDesc` | yes | Account currency (e.g. `"MVR"`, `"USD"`) |
| `benefName` | yes | Counterparty name — `null`, blank or literal `"null"` means none |
| `trxNumber2` | yes | Secondary reference; may be blank |
| `baseAmount` | server-only | Signed amount in MVR. Differs from `foreignAmount` on USD accounts. |
| `absAmount` | server-only | Unsigned MVR amount |
| `descr2` / `descr3` / `remarks` | server-only | Extra reference and narrative lines |
| `trxType` | server-only | Numeric transaction type (e.g. `800` Favara credit, `803` Favara transfer, `205` account-to-account) |
| `imageHash` | server-only | Counterparty image hash, fetchable via `P41` (see [07-profile.md](07-profile.md)) |
> On USD accounts `baseAmount` is the MVR equivalent — always use `foreignAmount` for the amount shown against the account.
---
+31 -18
View File
@@ -7,7 +7,7 @@ Fetch debit card information for the authenticated session.
## Endpoint
```
POST https://faisamobilex-wv.mib.com.mv/ajaxDebitCard/fetchCardInfos
POST https://faisamobilex-wv-v2.mib.com.mv/ajaxDebitCard/fetchCardInfos
```
---
@@ -17,7 +17,7 @@ POST https://faisamobilex-wv.mib.com.mv/ajaxDebitCard/fetchCardInfos
See [README](README.md) for cookie and AJAX header format.
```
Referer: https://faisamobilex-wv.mib.com.mv//debitCards?dashurl=1
Referer: https://faisamobilex-wv-v2.mib.com.mv/debitCards
```
---
@@ -38,15 +38,21 @@ Referer: https://faisamobilex-wv.mib.com.mv//debitCards?dashurl=1
```json
{
"success": true,
"responseCode": "1",
"reasonCode": "101",
"reasonText": "Single Debit card fetch success!",
"total_count": "1",
"pos": "1",
"data": [
{
"cardId": "CARD001",
"maskedCardNumber": "4111 **** **** 1234",
"cardStatus": "A",
"cardType": "D",
"cardTypeDesc": "Debit Card",
"customerId": "C000001",
"phoneNumber": "9600000001",
"cardId": "100000000001",
"maskedCardNumber": "4023XXXXXXXX1234",
"cardStatus": "CHST0",
"cardType": "70",
"contractId": "552",
"cardTypeDesc": "VISA EMV Corporate",
"customerId": "40000001",
"phoneNumber": "009609600001",
"cardHolderName": "MOHAMED ALI"
}
]
@@ -64,9 +70,10 @@ Referer: https://faisamobilex-wv.mib.com.mv//debitCards?dashurl=1
|---|---|---|
| `cardId` | `string` | Internal card identifier |
| `maskedCardNumber` | `string` | Partially masked card number for display |
| `cardStatus` | `string` | Card status (`A` = Active) |
| `cardType` | `string` | Card type code (e.g. `D` = Debit) |
| `cardTypeDesc` | `string` | Human-readable card type (e.g. `"Debit Card"`) |
| `cardStatus` | `string` | Card status code (`CHST0` = valid, `CHST20` = temporarily blocked by client) |
| `cardType` | `string` | Numeric card type code (e.g. `"70"`) |
| `contractId` | `string` | Card contract ID (not consumed) |
| `cardTypeDesc` | `string` | Human-readable card type (e.g. `"VISA EMV Corporate"`) |
| `customerId` | `string` | Customer ID |
| `phoneNumber` | `string` | Registered phone number |
| `cardHolderName` | `string` | Name on card |
@@ -84,14 +91,14 @@ Referer: https://faisamobilex-wv.mib.com.mv//debitCards?dashurl=1
## Freeze / Unfreeze Card
```
POST https://faisamobilex-wv.mib.com.mv/ajaxDebitCard/freeze
POST https://faisamobilex-wv.mib.com.mv/ajaxDebitCard/unfreeze
POST https://faisamobilex-wv-v2.mib.com.mv/ajaxDebitCard/freeze
POST https://faisamobilex-wv-v2.mib.com.mv/ajaxDebitCard/unfreeze
```
Two endpoints — same shape. Pick by intent. Source: `MibCardsClient.kt:74-103`.
```
Referer: https://faisamobilex-wv.mib.com.mv//debitCards/manage?cardId=<cardId>&dashurl=1
Referer: https://faisamobilex-wv-v2.mib.com.mv/debitCards/manage?cardId=<cardId>
```
### Request Body (form-urlencoded)
@@ -106,16 +113,22 @@ Referer: https://faisamobilex-wv.mib.com.mv//debitCards/manage?cardId=<cardId>&d
```json
{
"success": true,
"reasonText": "Card frozen successfully",
"currentStatusCode": "F"
"responseCode": "1",
"reasonCode": "502",
"reasonText": "Card Frozen",
"currentStatus": "TEMPRORY BLOCKED BY CLIENT",
"currentStatusCode": "CHST20"
}
```
Unfreeze returns `reasonText: "Card Activated"`, `currentStatus: "VALID CARD"`, `currentStatusCode: "CHST0"`.
| Field | Description |
|---|---|
| `success` | `true` on success |
| `reasonText` | Human-readable message (also returned on failure — show to user) |
| `currentStatusCode` | New card status code after the action (e.g. `"F"` = frozen, `"A"` = active) |
| `currentStatus` | Human-readable status after the action |
| `currentStatusCode` | New card status code (`"CHST20"` = frozen, `"CHST0"` = active) |
---
+1 -1
View File
@@ -7,7 +7,7 @@ Fetch active financing deals. Unlike other data endpoints, this returns an HTML
## Endpoint
```
GET https://faisamobilex-wv.mib.com.mv/financing?dashurl=1
GET https://faisamobilex-wv-v2.mib.com.mv/financing?dashurl=1
```
---
+84 -59
View File
@@ -1,92 +1,117 @@
# Personal Profile
This document covers two unrelated profile-adjacent surfaces:
This document covers two profile-adjacent surfaces:
1. **Personal profile** — an HTML page on the WebView host, scraped for display.
1. **Personal details** — full name, email, mobile number and National ID, shown on the login details screen.
2. **Profile image management** — three encrypted-API endpoints (`P40`/`P41`/`P42`) that fetch, upload, and delete the avatar.
---
## 1. Personal Profile (HTML scrape)
## 1. Personal Details
Fetch the user's personal profile details. This endpoint returns an HTML page; data is extracted via HTML scraping.
The details come from two sources and are fetched once at login, then cached (`MibProfileClient.kt`).
### Endpoint
| Field | Source |
|---|---|
| Full name | `A41` login init response — `fullName` |
| Email | Alias suggestions — type `1` (falls back to `A41` `email`) |
| Mobile number | Alias suggestions — type `0` |
| National ID | Alias suggestions — type `3` |
```
GET https://faisamobilex-wv.mib.com.mv/personalProfile
### Full name — `A41`
The `A41` login init response (see [02-login.md](02-login.md)) carries the user's details at the root:
```json
{
"fullName": "Mohamed Ali",
"email": "user@example.com",
"lastLoginTime": "2026-05-16 15:10:25",
"customerImgHash": "<image hash>",
"roleName": "Consumer Premium"
}
```
### Authentication
Session cookies only — no additional AJAX headers required.
### Alias Suggestions — `getAliasTypes`
```
Cookie: mbmodel=IOS-1.0; xxid=<session_xxid>; IBSID=<session_xxid>; mbnonce=<nonceGenerator>; time-tracker=597
POST https://faisamobilex-wv-v2.mib.com.mv/ajaxAlias/getAliasTypes
```
### Response
WebView session cookies and AJAX headers (see [README](README.md)). Empty POST body.
**Content-Type:** `text/html; charset=UTF-8`
The page contains an `<h5>` with the user's full name and `<span>` elements with labelled fields.
### Parsing Strategy
**Full name** — extracted from:
```html
<h5 class="mb-1 text-dark fw-semibold">Mohamed Ali</h5>
```
Referer: https://faisamobilex-wv-v2.mib.com.mv/Alias/Alias?dashurl=1
```
Regex:
```kotlin
Regex("""<h5 class="mb-1 text-dark fw-semibold">\s*([^<]+)\s*</h5>""")
The suggestions belong to the **currently selected** profile and differ between personal and business profiles. Select the personal profile (`profileType: "0"`) with `P47` first (see [03-accounts.md](03-accounts.md)).
#### Response
```json
{
"success": true,
"responseCode": "2",
"reasonCode": "203",
"reasonText": "Alias Types Search Success.",
"data": [
{ "id": 0, "aliasTypeName": "Mobile Number", "suggestedAlias": "+9609600001" },
{ "id": 1, "aliasTypeName": "E-Mail", "suggestedAlias": "user@example.com" },
{ "id": 3, "aliasTypeName": "National ID", "suggestedAlias": "A000001" }
]
}
```
**Labelled fields** — each follows this pattern:
```html
<span ...><b ...>Username:</b ...>...<span ...>myusername</span>
```
Regex (used for each label):
```kotlin
Regex(
"""<span[^>]*>\s*<b[^>]*>\s*$label\s*</b[^>]*>.*?<span[^>]*>([^<]+)</span>""",
setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)
)
```
### Extracted Fields
| Label in HTML | Field | Description |
| `id` | `aliasTypeName` | Used as |
|---|---|---|
| `Username:` | `username` | Login username |
| `Email:` | `email` | Registered email address |
| `Mobile no:` | `mobile` | Registered mobile number |
| `Enrolled:` | `enrolled` | Enrollment date or status |
| `0` | Mobile Number | Mobile number (with `+960` prefix) |
| `1` | E-Mail | Email |
| `3` | National ID | ID card number |
Combined with the `fullName` from the `<h5>`:
A type may be missing from `data` when the user has no value for it.
```kotlin
data class MibPersonalProfile(
val fullName: String,
val username: String,
val email: String,
val mobile: String,
val enrolled: String
)
### Registered Aliases — `ajaxAlias/main`
```
POST https://faisamobilex-wv-v2.mib.com.mv/ajaxAlias/main
```
### Notes
Lists the user's registered Favara aliases. Not consumed by the app.
- Returns `null` if the response cannot be parsed (network error or unexpected HTML structure).
- This endpoint does not have a JSON equivalent — scraping is the only method.
Body: `page=1&search=&searchAccount=&searchAliasType=&sort=AlsId&sortDir=asc&start=1&end=20&includeCount=1`
```json
{
"success": true,
"reasonText": "Favara ID Search Success.",
"data": [
{
"AlsBfy": { "Tp": "MSISDN", "Id": "+9609600001" },
"BfyNm": "Mohamed",
"BfySnm": "Ali",
"CdtrAcct": { "Acct": "90100000000000000", "FinInstnId": "MADVMVMV" },
"AcctHldrId": "A000001",
"AcctHldrIdTp": "IDENTITY_NUMBER",
"AcctHldrTp": "INDIVIDUAL",
"AdrLine": "<address>",
"Ctry": "MDV",
"RegDtTm": "2026-05-12T20:07:44",
"AlsSt": "ACTIVE",
"PhneNb": "+9609600001",
"EmailAdr": "user@example.com"
}
],
"Pg": { "Size": 20, "TtlElms": 1, "TtlPgs": 1, "Nmb": 0 }
}
```
`AlsBfy.Tp` is the alias type, e.g. `MSISDN` or `NATIONAL_ID`.
---
## 2. Profile Image Management
The avatar lives on the **encrypted API** (`faisanet.mib.com.mv`), not the WebView host. Three `sfunc=n` route paths cover fetch, upload, and delete. All three follow the standard encrypted-request format (see [01-encryption.md](01-encryption.md) and [02-login.md](02-login.md)) and include the standard `baseData` fields (`nonce`, `appId`, `sodium`, `routePath`, `xxid`).
The avatar lives on the **encrypted API** (`faisamobilex-smvc-v2.mib.com.mv`), not the WebView host. Three `sfunc=n` route paths cover fetch, upload, and delete. All three follow the standard encrypted-request format (see [01-encryption.md](01-encryption.md) and [02-login.md](02-login.md)) and include the standard `baseData` fields (`nonce`, `appId`, `sodium`, `routePath`, `xxid`).
| `routePath` | Operation | Source |
|---|---|---|
@@ -101,10 +126,10 @@ The same conceptual value — "the hash that identifies a customer's avatar" —
| Source endpoint | Field name |
|---|---|
| `A41` login init (`operatingProfiles[]`) | `customerImage` |
| `A41` login init (root) | `customerImgHash` |
| `C41` registration init (root) | `customerImgHash` |
| `ajaxBeneficiary/main` (contacts list) | `customerImgHash` |
Both are non-empty hash strings that can be passed straight into `P41` as `imageHash`. Treat them as the same value with two names.
Both are non-empty hash strings that can be passed straight into `P41` as `imageHash`. Treat them as the same value with two names. Contact photos use a separate route, `B46` (see [09-contacts.md](09-contacts.md)).
### Fetch — `routePath: P41`
+209 -149
View File
@@ -2,197 +2,244 @@
Two-step process: look up the recipient to validate the account and get the holder name, then execute the transfer.
All endpoints are on the WebView subdomain. See [README](README.md) for cookie and AJAX header format.
```
Referer: https://faisamobilex-wv.mib.com.mv/transfer/quick
```
All endpoints are on the encrypted API (`sfunc=n`, session key). See [README](README.md) for the request format. The source account must belong to the **currently selected** profile — call `P47` for its profile first (see [03-accounts.md](03-accounts.md)).
---
## Step 1 — Account Lookup
## Step 1 — Account Lookup — `routePath: B40`
The lookup endpoint depends on the format of the input:
One route resolves every supported input:
| Input format | Endpoint | Body field |
| Input format | Example | Resolves to |
|---|---|---|
| Starts with `7`, exactly 13 digits | `AjaxAlias/getIPSAccount` | `benefAccount` |
| Starts with `9`, exactly 17 digits | `ajaxBeneficiary/getAccountName` | `accountNo` |
| Starts with `7` or `9`, exactly 7 digits | `AjaxAlias/getAlias` | `aliasName` |
| Starts with `A` followed by 6 digits | `AjaxAlias/getAlias` | `aliasName` |
| Contains `@` (email) | `AjaxAlias/getAlias` | `aliasName` |
| Starts with `7`, exactly 13 digits | `7700000000000` | BML / local bank account |
| Starts with `9`, exactly 17 digits | `90100000000000000` | MIB account |
| Starts with `7` or `9`, exactly 7 digits | `9600001` | Favara mobile alias |
| `A` followed by 6 digits | `A000001` | Favara National ID alias |
| Contains `@` | `user@example.com` | Favara email alias |
---
### 1a. IPS Account — BML / local bank (13 digits, starts with `7`)
```
POST https://faisamobilex-wv.mib.com.mv/AjaxAlias/getIPSAccount
```
Body: `benefAccount=7700000000000`
**Response**:
**Request**:
```json
{
"success": true,
"responseCode": "2",
"accountName": "ACCOUNT HOLDER NAME",
"bankBic": "MALBMVMV"
}
```
- `accountName` — account holder name
- `bankBic` — bank BIC (e.g. `MALBMVMV` for BML)
- Account number is the input itself — not returned in response
Use `bankNo=3` and `transferLocal` for the transfer.
> **USD cross-bank accounts**: `getIPSAccount` only succeeds for **MVR** cross-bank accounts. A 13-digit `7…` account that is denominated in USD returns `success: false` here and cannot be resolved via IPS at all. The client hardcodes `currency = "MVR"` for IPS results (`MibTransferClient.kt:135-137`). For USD BML→MIB transfers the user must first save a BML contact (see Notes below).
---
### 1b. MIB Internal Account (17 digits, starts with `9`)
```
POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/getAccountName
```
Body: `accountNo=90100000000000000`
**Response**:
```json
{
"success": true,
"accountName": "ACCOUNT HOLDER NAME",
"currencyCode": "462"
}
```
- `accountName` may be at root level or inside a `data` object — check both (`MibTransferClient.kt:160-161`)
- `currencyCode` may also be at root level or inside `data` (`MibTransferClient.kt:162-163`). `"462"` = MVR, `"840"` = USD. The client maps this into `MibIpsAccountInfo.currency` ∈ `{"MVR", "USD", ""}` — this is the **MIB→MIB USD detection** fix from commit `16fd909`.
- Bank is always MIB (`MADVMVMV`)
Use `bankNo=2` and `transferInternal` for the transfer.
---
### 1c. Favara Alias — shortcodes, A-IDs, emails
```
POST https://faisamobilex-wv.mib.com.mv/AjaxAlias/getAlias
```
Body: `aliasName=<alias>`
**Response**:
```json
{
"success": true,
"responseCode": "2",
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"BfyNm": "Account Holder Name",
"CdtrAcct": {
"Acct": "90100000000000000",
"FinInstnId": "MADVMVMV"
}
"search": "7700000000000",
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random 20-bit int>",
"routePath": "B40",
"xxid": "<session xxid>"
}
}
```
- `BfyNm` — beneficiary name (trim whitespace)
- `CdtrAcct.Acct` — resolved account number to use for the transfer
- `CdtrAcct.FinInstnId` — bank BIC (`MADVMVMV` = MIB, `MALBMVMV` = BML)
### Input Rules
Use `bankNo=2` (MIB) or `3` (BML/local) depending on `FinInstnId`, and the matching transfer endpoint.
- **Mobile aliases** must be the bare 7 digits. `+960 9600001` and `+9609600001` return no match.
- **National ID aliases** need an uppercase `A` — `a000001` returns no match, `A000001` resolves. The client uppercases the prefix before sending (`MibTransferClient.kt`).
- Partial input (e.g. `9600`) returns an empty `data` array.
---
### Lookup Errors
All three lookup endpoints return `success: false` with a human-readable `reasonText` on failure:
```json
{
"success": false,
"reasonText": "Account not found"
}
```
Always show `reasonText` directly to the user.
---
## Step 2 — Execute Transfer
Two endpoints depending on the destination bank:
| `bankNo` | Endpoint | Destination |
|---|---|---|
| `2` | `ajaxTransfer/transferInternal` | MIB internal account |
| `3` | `ajaxTransfer/transferLocal` | BML or other local bank |
```
POST https://faisamobilex-wv.mib.com.mv/ajaxTransfer/transferInternal
POST https://faisamobilex-wv.mib.com.mv/ajaxTransfer/transferLocal
```
### Request Body (form-urlencoded)
| Field | Description |
|---|---|
| `benefName` | Recipient name (from lookup) |
| `benefNo` | `0` (not a saved contact) |
| `fromAccountNo` | Source account number |
| `benefAccountNo` | Destination account number |
| `transferCy` | Currency numeric code (`"462"` = MVR, `"840"` = USD) |
| `benefCurrencyCode` | Same as `transferCy` |
| `amount` | Amount as string (e.g. `"100.00"`) |
| `bankNo` | `2` = MIB internal, `3` = local/BML |
| `purpose` | Transfer purpose; send `"-"` if blank |
| `otp` | OTP from the user |
| `otpType` | `"3"` (SMS/authenticator OTP) |
### Response — Success
### Response
**BML account**:
```json
{
"success": true,
"data": [
{
"trxId": "TRX20260516001",
"date": "2026-05-16 15:10:25"
"benefName": "ACCOUNT HOLDER NAME",
"benefAccount": "7700000000000",
"bankCode": "MALBMVMV",
"benefBankName": "Bank of Maldives"
}
]
}
```
**MIB account**:
```json
{
"success": true,
"data": [
{
"benefName": "ACCOUNT HOLDER NAME",
"benefAccount": "90100000000000000",
"bankCode": "MIB",
"benefBankName": "MIB",
"transferCy": "462",
"transferCyDesc": "MVR"
}
]
}
```
**Favara alias** (resolving to an MIB account):
```json
{
"success": true,
"data": [
{
"benefName": "Account Holder Name ",
"benefAccount": "90100000000000000",
"bankCode": "MADVMVMV",
"benefBankName": "Maldives Islamic Bank PLC."
}
]
}
```
| Field | Description |
|---|---|
| `benefName` | Account holder name — trim whitespace |
| `benefAccount` | Resolved account number to use for the transfer |
| `bankCode` | `"MIB"` or a bank BIC (`MADVMVMV` = MIB, `MALBMVMV` = BML). Normalize `"MIB"` to `MADVMVMV`. |
| `benefBankName` | Bank display name |
| `transferCy` / `transferCyDesc` | Account currency — present for MIB account lookups only |
### Lookup Errors
No match returns `success: true` with an empty `data` array:
```json
{ "success": true, "data": [] }
```
Show "account not found" in that case.
### Currency
| Lookup | Currency |
|---|---|
| MIB account | From `transferCyDesc` (`"MVR"` / `"USD"`) |
| Favara alias → MIB | Not returned — unknown |
| BML / local | `"MVR"` — cross-bank lookups only resolve MVR accounts |
> **USD cross-bank accounts**: a BML account denominated in USD cannot be resolved here. For USD BML→MIB transfers the user must first save a BML contact (see Notes below).
---
## Step 2 — Execute Transfer
Two routes depending on the destination bank:
| `bankNo` | `routePath` | Destination |
|---|---|---|
| `2` | `T50` | MIB internal account |
| `3` | `T51` | BML or other local bank (via Favara) |
**Request**:
```json
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"benefName": "ACCOUNT HOLDER NAME",
"benefNo": "0",
"fromAccountNo": "90101000000001000",
"benefAccountNo": "7700000000000",
"transferCy": "462",
"benefCurrencyCode": "462",
"amount": "100.00",
"bankNo": "3",
"purpose": "",
"otp": "123456",
"otpType": "3",
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random 20-bit int>",
"routePath": "T51",
"xxid": "<session xxid>"
}
}
```
| Field | Description |
|---|---|
| `benefName` | Recipient name (from lookup) |
| `benefNo` | `"0"` (not a saved contact) |
| `fromAccountNo` | Source account number |
| `benefAccountNo` | Destination account number |
| `transferCy` | Currency numeric code (`"462"` = MVR, `"840"` = USD) |
| `benefCurrencyCode` | Same as `transferCy` |
| `amount` | Amount as string (e.g. `"100.00"`, `".01"`) |
| `bankNo` | `"2"` = MIB internal, `"3"` = local/BML |
| `purpose` | Transfer remarks; may be empty |
| `otp` | Current TOTP code |
| `otpType` | `"3"` (authenticator OTP) |
### Response — Success
**`T50`** (internal):
```json
{
"success": true,
"responseCode": "2",
"reasonCode": "2011",
"reasonText": "Internal transfer success",
"data": [
{ "date": "2026-05-16 15:10:25", "trxId": "97999863", "availableBalance": "37.41" }
],
"accountBalance": [ ... ]
}
```
**`T51`** (local):
```json
{
"success": true,
"responseCode": "2",
"reasonCode": "2011",
"reasonText": "Local transfer completed successfully via Favara",
"data": [
{ "date": "2026-05-16 15:10:25", "trxId": "817054125" }
]
}
```
| Field | Description |
|---|---|
| `trxId` | Transaction ID |
| `date` | Completion timestamp |
| `availableBalance` | `T50` only — source account balance after the transfer |
| `accountBalance` | `T50` only — refreshed balances for the selected profile (same shape as in [03-accounts.md](03-accounts.md)) |
### Response — Failure
Failures return HTTP `400` with an encrypted body:
```json
{
"success": false,
"reasonText": "Insufficient balance"
"responseCode": "1",
"reasonCode": "213",
"reasonText": "Invalid OTP",
"data": [ { "otpId": "50487016" } ]
}
```
`reasonText` contains the error reason. On HTTP `419`, the session has expired — re-login required.
| `reasonCode` | `reasonText` | Meaning |
|---|---|---|
| `213` | `Invalid OTP` | Wrong or stale OTP |
| `707` | `That OTP is already used!` | The OTP was already accepted for an earlier request |
| `201` | `Transaction currency does not match From Account currency!` | `transferCy` doesn't match the source account |
Show `reasonText` to the user.
### OTP Retry
On `213` or `707` the client waits for the next 30-second TOTP window (plus 1 second for clock skew), generates a new code and resends the transfer **once**. A second failure of any kind is shown to the user (`MibTransferHandler.kt`).
---
## Transfer Type Summary
| Recipient | Lookup endpoint | `bankNo` | Transfer endpoint |
| Recipient | Lookup | `bankNo` | Transfer |
|---|---|---|---|
| MIB (17-digit `9…`) | `getAccountName` | `2` | `transferInternal` |
| BML (13-digit `7…`) | `getIPSAccount` | `3` | `transferLocal` |
| Favara alias → MIB | `getAlias` | `2` | `transferInternal` |
| Favara alias → BML | `getAlias` | `3` | `transferLocal` |
| MIB (17-digit `9…`) | `B40` | `2` | `T50` |
| BML (13-digit `7…`) | `B40` | `3` | `T51` |
| Favara alias → MIB | `B40` | `2` | `T50` |
| Favara alias → BML | `B40` | `3` | `T51` |
---
@@ -205,23 +252,36 @@ POST https://faisamobilex-wv.mib.com.mv/ajaxTransfer/transferLocal
---
## Other Transfer-Screen Routes
Called by the official app when opening the transfer screen. Not consumed by thijooree.
| `routePath` | Request | Returns |
|---|---|---|
| `T63` | — | Currencies with rates (`numericCode`, `code`, `name`, `rate`) |
| `T66` | `currencyCode` | Sell rate for a currency (`sellRate`) |
| `T70` | — | Upload settings (`numFiles`, `fileSize`) |
| `T71` | optional `transferCy` | AML thresholds (`eddAmount`, `cddAmount`, `currencyCode`) |
---
## `MibIpsAccountInfo` (client model)
All three lookups return this unified structure (`MibModels.kt:42-47`):
`B40` lookups are mapped into this structure (`MibModels.kt`):
| Field | Description |
|---|---|
| `accountName` | Account holder name (trimmed) |
| `accountNumber` | Resolved account number |
| `bankId` | Bank BIC (`MADVMVMV` = MIB, `MALBMVMV` = BML, etc.) |
| `currency` | `"MVR"`, `"USD"`, or `""` (unknown). Populated from `currencyCode` for MIB internal lookups; hardcoded `"MVR"` for IPS lookups; default `""` for alias lookups. |
| `currency` | `"MVR"`, `"USD"`, or `""` (unknown) — see Currency above |
---
## Notes
- **BML → MIB USD transfers** require a saved BML contact first. Because `getIPSAccount` rejects USD accounts (`success: false`), the app cannot validate the BML USD account number directly. The workaround in `TransferFragment.kt` is to call `MibContactsClient.createContact` (see [09-contacts.md](09-contacts.md)) to auto-add the BML account as a beneficiary, then transfer to that beneficiary. Introduced in commit `16fd909`.
- **Session expiry**: HTTP `419` on either lookup or transfer means the session expired. See [README](README.md) for the unified expiry detection rules.
- **BML → MIB USD transfers** require a saved BML contact first. Because USD BML accounts can't be resolved via `B40`, the app cannot validate the account number directly. The workaround in `TransferFragment.kt` is to add the BML account as a beneficiary (see [09-contacts.md](09-contacts.md)), then transfer to that beneficiary.
- **Session expiry**: see [README](README.md) for the unified expiry detection rules.
---
+187 -132
View File
@@ -1,62 +1,58 @@
# Contacts (Beneficiaries)
Manage the user's saved beneficiary list. All endpoints use WebView session auth — see [README](README.md).
Manage the user's saved beneficiary list. All endpoints are on the encrypted API (`sfunc=n`, session key) — see [README](README.md) for the request format.
```
Referer: https://faisamobilex-wv.mib.com.mv/beneficiary?dashurl=1
```
Contacts belong to the **currently selected** profile — call `P47` for the profile first (see [03-accounts.md](03-accounts.md)).
Every write returns a `beneficiaryVersion` counter that increments on each change.
---
## List Categories
```
POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/getCategories
```
Empty POST body.
### Response
## List Contacts — `routePath: B41`
**Request**:
```json
{
"success": true,
"data": [
{ "id": "100001", "categoryName": "Myself", "numBenef": "2" },
{ "id": "100002", "categoryName": "Friends", "numBenef": "10" },
{ "id": "100003", "categoryName": "Business", "numBenef": "8" },
{ "id": "100004", "categoryName": "Family", "numBenef": "5" }
]
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"start": 1,
"end": 10,
"benefType": "A",
"search": "",
"includeCount": 1,
"searchCategoryId": 0,
"sortBenef": "",
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random 20-bit int>",
"routePath": "B41",
"xxid": "<session xxid>"
}
}
```
| Field | Description |
|---|---|
| `id` | Category ID — use as `searchCategoryId` when filtering contacts |
| `categoryName` | Display name |
| `numBenef` | Number of beneficiaries in this category (string) |
---
## List Contacts
```
POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/main
```
### Request Body (form-urlencoded)
| Field | Example | Description |
|---|---|---|
| `page` | `1` | Page number (1-based) |
| `search` | `` | Search query (empty = all) |
| `searchCategoryId` | `0` | Category filter (`0` = all) |
| `start` | `1` | First row (`rnum`, 1-based, inclusive) |
| `end` | `10` | Last row (inclusive) |
| `benefType` | `A` | `A`=All, `L`=Local, `I`=Internal, `S`=Swift |
| `sortBenef` | `name` | Sort field |
| `sortDir` | `asc` | Sort direction |
| `start` | `1` | Start record index (1-based) |
| `end` | `100` | End record index |
| `search` | `` | Search query (empty = all) |
| `includeCount` | `1` | Include `total_count` |
| `searchCategoryId` | `0` | Category filter (`0` = all) |
| `sortBenef` | `` | Sort field (empty = default) |
### Pagination
Rows are selected by their 1-based `rnum`, both ends inclusive. The client pages in windows of 10:
| Page | `start` | `end` |
|---|---|---|
| 1 | `1` | `10` |
| 2 | `11` | `20` |
| N | `(N-1)*10 + 1` | `N*10` |
The last row of the final page has `last: "1"`. Stop there, or when total fetched equals `total_count`.
### Beneficiary Types
@@ -71,24 +67,42 @@ POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/main
```json
{
"success": true,
"total_count": "48",
"responseCode": "1",
"reasonCode": "103",
"reasonText": "beneficiary retrieval success",
"total_count": "47",
"pos": "0",
"data": [
{
"benefNo": "100001",
"benefName": "Person Name",
"benefNickName": "Nickname",
"benefAccount": "7700000000001",
"benefType": "L",
"bankColor": "#AC0000",
"benefBankName": "Bank of Maldives PLC",
"bankCode": "BML",
"benefSwiftCode": "MALBMVMV",
"benefStatus": "A",
"benefBankId": "3",
"benefNickName": "Nickname",
"benefSwiftCode": "MALBMVMV",
"transferCy": "462",
"transferCyDesc": "MVR",
"customerImgHash": "abcd1234hash",
"benefCategoryID": "100002"
"customerImgHash": "<image hash>",
"benefImgHash": "<image hash>",
"benefCategoryID": "100002",
"createdDate": "2025-05-25 10:38:49",
"rnum": "1",
"last": "0"
}
],
"categories": [
{
"id": "100002",
"categoryName": "Friends",
"icon": "f095",
"createdDate": "2023-05-18 14:35:22",
"modifiedDate": "2023-05-19 12:06:05",
"numBenef": "23"
}
]
}
@@ -96,37 +110,51 @@ POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/main
| Field | Consumed | Description |
|---|---|---|
| `benefNo` | yes | Unique beneficiary ID — use for delete |
| `benefNo` | yes | Unique beneficiary ID — use for update and delete |
| `benefName` | yes | Legal/full name; used as fallback when nickname is blank |
| `benefNickName` | yes | User-assigned nickname (prefer over `benefName` for display) |
| `benefAccount` | yes | Account number |
| `benefType` | yes | `L`, `I`, or `S` |
| `bankColor` | yes | Hex color for placeholder avatar background |
| `benefBankName` | yes | Bank display name. Shown as transfer subtitle and contact detail (`MibContactParser.kt:23, 26`). |
| `benefBankName` | yes | Bank display name. Shown as transfer subtitle and contact detail (`MibContactParser.kt`). |
| `bankCode` | yes | Short bank code (e.g. `"BML"`, `"MIB"`) |
| `benefStatus` | yes | Beneficiary status (`"A"` = active) |
| `transferCyDesc` | yes | Currency (e.g. `"MVR"`, `"USD"`) |
| `customerImgHash` | yes | Hash for fetching profile photo. May be missing, blank, or the literal **string** `"null"` — the client filters all three (`MibContactsClient.kt:120`) so downstream code only sees a real hash or `null`. |
| `customerImgHash` | yes | Hash for fetching the contact photo via `B46`. May be missing, blank, or the literal **string** `"null"` — the client filters all three so downstream code only sees a real hash or `null`. |
| `benefCategoryID` | yes | Category ID — `"0"` means uncategorized |
| `benefSwiftCode` | server-only | SWIFT BIC of the beneficiary's bank — present in payload, not read by the app |
| `benefImgHash` | server-only | Photo set on the beneficiary itself; equals `customerImgHash` when set |
| `benefSwiftCode` | server-only | SWIFT BIC — present in payload, not read by the app |
| `benefBankId` | server-only | Numeric bank ID — not consumed |
| `transferCy` | server-only | Currency numeric code (e.g. `"462"`) — only `transferCyDesc` is consumed |
| `rnum` / `last` | server-only | Row number and last-row flag, used for paging |
### Categories
Every `B41` response carries the full category list in `categories` — there is no separate categories call.
| Field | Description |
|---|---|
| `id` | Category ID — use as `searchCategoryId` / `categoryId` |
| `categoryName` | Display name |
| `numBenef` | Number of beneficiaries in this category (string) |
| `icon` | Icon code |
---
## Get Profile Image
## Get Contact Photo — `routePath: B46`
**Request** — route fields:
```json
{ "imageHash": "<customerImgHash>", "routePath": "B46" }
```
POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/getProfileImage
```
Body: `imageHash=<customerImgHash>`
### Response
```json
{
"success": true,
"msg": "image found",
"message": "image found",
"profileImage": "<base64-encoded JPEG>"
}
```
@@ -135,98 +163,125 @@ Body: `imageHash=<customerImgHash>`
---
## Create Contact
## Create Contact — `routePath: B43`
**Request** — route fields:
```json
{
"benefName": "RECIPIENT NAME",
"benefAccount": "7700000000001",
"bankNo": "3",
"nickName": "Nickname",
"transferCy": "462",
"categoryId": "0",
"image": "<base64 JPEG, optional>",
"bankName": "Bank of Maldives PLC",
"bankSwift": "MALBMVMV",
"routePath": "B43"
}
```
POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/createLocalBeneficiary
```
```
Referer: https://faisamobilex-wv.mib.com.mv/beneficiary/createNew
```
### Request Body (form-urlencoded)
| Field | Description |
|---|---|
| `benefType` | `"I"` = MIB internal, `"L"` = local/BML |
| `benefName` | Full name (from account lookup) |
| `benefAccount` | Beneficiary account number |
| `benefName` | Full name |
| `nickName` | Display nickname |
| `bankNo` | `2` = MIB, `3` = BML/local |
| `transferCy` | Currency numeric code (`"462"` = MVR) |
| `bankNo` | `"2"` = MIB, `"3"` = BML |
| `nickName` | Display nickname — **max 20 characters** |
| `transferCy` | Currency numeric code (`"462"` = MVR, `"840"` = USD) |
| `categoryId` | Category ID (`"0"` = uncategorized) |
| `imageSet` | `"1"` if image provided, `"0"` otherwise |
| `image` | Base64-encoded image (empty string if none) |
| `benefIban` | Leave blank |
| `benefAddress` | Leave blank |
| `benefCity` | Leave blank |
| `benefCountry` | `"4"` |
| `benefBankSwift` | Leave blank |
| `benefBankName` | Leave blank |
| `benefBankBranch` | Leave blank |
| `benefBankAddress` | Leave blank |
| `benefBankCity` | Leave blank |
| `benefBankCountry` | `"4"` |
| `intBankSwift` | Leave blank |
| `intBankName` | Leave blank |
| `intBankAddress` | Leave blank |
| `intBankBranch` | Leave blank |
| `intBankCity` | Leave blank |
| `intBankCountry` | `"4"` |
| `transferCySwift` | `"840"` (USD numeric — static) |
| `email` | Leave blank |
| `contactNumber` | Leave blank |
| `website` | Leave blank |
| `image` | Base64 JPEG photo. Omit the field when there is no photo. |
| `bankName` | Bank name from the login bank list (`A41` `bankData`) |
| `bankSwift` | Bank BIC from the same list |
### Response
```json
{ "success": true }
```
`success: true` confirms the contact was saved.
---
## Delete Contact
```
POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/deleteBeneficiary
```
Body: `benefNo=<benefNo>`
### Response
```json
{ "success": true }
```
---
## Contact Stats
```
POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/getStats
```
Empty POST body.
| `bankNo` | `bankName` | `bankSwift` |
|---|---|---|
| `2` | Maldives Islamic Bank | `MADVMVMV` |
| `3` | Bank of Maldives PLC | `MALBMVMV` |
### Response
```json
{
"success": true,
"data": [
{ "type": "L", "count": "30" },
{ "type": "I", "count": "10" },
{ "type": "S", "count": "2" }
]
"responseCode": "1",
"reasonCode": "102",
"reasonText": "beneficiary creation success",
"data": [ { "benefNo": "1336023", "benefNickName": "Nickname", "customerImgHash": "<image hash>", ... } ],
"beneficiaryVersion": 8
}
```
Gives counts per beneficiary type. Useful for showing tab badges.
### Errors
A nickname over 20 characters is rejected with HTTP `400`:
```json
{ "success": false, "responseCode": "2", "reasonCode": "201", "reasonText": "invalid nick name", "errorKey": "nickName" }
```
---
## Update Contact — `routePath: B52`
Same fields as `B43`, plus:
| Field | Description |
|---|---|
| `benefNo` | Beneficiary to update |
| `imageChanged` | `"1"` when a new `image` is sent, `"0"` to keep the current photo |
**Request** — route fields:
```json
{
"benefName": "RECIPIENT NAME",
"benefAccount": "7700000000001",
"bankNo": "3",
"nickName": "New nickname",
"transferCy": "462",
"categoryId": "100002",
"benefNo": "240301",
"imageChanged": "0",
"bankName": "Bank of Maldives PLC",
"bankSwift": "MALBMVMV",
"routePath": "B52"
}
```
### Response
```json
{
"success": true,
"responseCode": "1",
"reasonCode": "107",
"reasonText": "Beneficiary Updated Successfully",
"data": [ { "benefNo": "240301", "benefNickName": "New nickname", "customerImgHash": "<image hash>", ... } ],
"beneficiaryVersion": 6
}
```
Reload the list with `B41` afterwards to pick up the change.
---
## Delete Contact — `routePath: B48`
**Request** — route fields:
```json
{ "benefNo": "240301", "routePath": "B48" }
```
### Response
```json
{
"success": true,
"responseCode": "1",
"reasonCode": "108",
"reasonText": "Beneficiary deleted Successfully",
"beneficiaryVersion": 7
}
```
---
+2 -2
View File
@@ -9,7 +9,7 @@ Source: `MibActivityHistoryClient.kt`.
## Endpoint
```
POST https://faisamobilex-wv.mib.com.mv/aProfile/getPagedActivityHistory
POST https://faisamobilex-wv-v2.mib.com.mv/aProfile/getPagedActivityHistory
```
---
@@ -21,7 +21,7 @@ WebView session cookies (see [README](README.md)) plus `X-Requested-With: XMLHtt
Unlike most WebView AJAX calls, this endpoint sends **no `Referer`** and **no `Origin`** header.
```
Cookie: mbmodel=IOS-1.0; xxid=<session_xxid>; IBSID=<session_xxid>; mbnonce=<nonceGenerator>; time-tracker=597
Cookie: mbmodel=AND-1.0; xxid=<session_xxid>; IBSID=<session_xxid>; mbnonce=<nonceGenerator>; time-tracker=597
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Linux; Android <ver>; wv) AppleWebKit/537.36 ...
```
+36 -10
View File
@@ -12,8 +12,8 @@ MIB uses **two completely separate backends**:
| Backend | Base URL | Auth | Used for |
|---|---|---|---|
| Encrypted API | `https://faisamobilex-smvc-v2.mib.com.mv/index/` | Blowfish + DH session key | Login, key exchange |
| WebView host | `https://faisamobilex-wv.mib.com.mv` | Session cookies | Accounts, history, transfers, contacts, cards, financing |
| Encrypted API | `https://faisamobilex-smvc-v2.mib.com.mv/index/` | Blowfish + DH session key | Login, accounts, history, transfers, contacts, profile images |
| WebView host | `https://faisamobilex-wv-v2.mib.com.mv` | Session cookies | Cards, financing, personal details, activity history, keep-alive |
---
@@ -22,10 +22,24 @@ MIB uses **two completely separate backends**:
All calls to the encrypted API are `POST /` with `Content-Type: application/x-www-form-urlencoded; charset=utf-8` and form body:
```
sfunc=<function_code>&data=<url_encoded_base64_blowfish_ciphertext>
xxid=<session_xxid>&sfunc=<function_code>&data=<url_encoded_base64_blowfish_ciphertext>
```
The request JSON is encrypted with Blowfish (ECB, PKCS5) before sending. The response body is also base64-encoded Blowfish ciphertext.
`xxid` is omitted on the key exchanges (`sfunc=r` / `sfunc=i`); `sfunc=i` sends `key2` instead. The request JSON is encrypted with Blowfish (ECB, PKCS5) before sending. The response body is also base64-encoded Blowfish ciphertext — on failures too, which come back as HTTP `400` with an encrypted `success: false` body.
Every `sfunc=n` call is identified by a `routePath` inside the encrypted payload, alongside the standard fields:
```json
{
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random int>",
"routePath": "<route, e.g. A84>",
"xxid": "<session xxid>"
}
```
Route-specific fields are added to the same object. See [02-login.md](02-login.md) for the request headers.
Two keys are used:
@@ -40,17 +54,17 @@ See [01-encryption.md](01-encryption.md) for full details.
## WebView Session Auth
After login, all data endpoints use cookie-based auth on `faisamobilex-wv.mib.com.mv`:
After login, the WebView endpoints use cookie-based auth on `faisamobilex-wv-v2.mib.com.mv`:
```
Cookie: mbmodel=IOS-1.0; xxid=<session_xxid>; IBSID=<session_xxid>; mbnonce=<nonceGenerator>; time-tracker=597
Cookie: mbmodel=AND-1.0; xxid=<session_xxid>; IBSID=<session_xxid>; mbnonce=<nonceGenerator>; time-tracker=597
```
These values come from the login flow — `xxid` and `nonceGenerator` from the DH key exchange response.
**Cookie notes:**
- `time-tracker=597` is a **hardcoded constant** in the client. Every WebView client (transfers, contacts, cards, activity, etc.) sends the literal value `597` — it is not computed or rotated.
- `time-tracker=597` is a **hardcoded constant** in the client. Every WebView client (cards, financing, activity, etc.) sends the literal value `597` — it is not computed or rotated.
- `mbnonce` is the **unmodified `nonceGenerator` string** from the key-exchange response. It does **not** carry a freshly-computed per-request nonce. The actual per-request nonce (derived via the algorithm in [01-encryption.md](01-encryption.md)) only appears inside the encrypted payloads of `sfunc=n` calls on the encrypted API — WebView endpoints have no nonce field.
### WebView AJAX Headers
@@ -60,7 +74,7 @@ All AJAX `POST` calls also require:
```
X-Requested-With: XMLHttpRequest
Accept: */*
Origin: https://faisamobilex-wv.mib.com.mv
Origin: https://faisamobilex-wv-v2.mib.com.mv
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
```
@@ -79,6 +93,18 @@ A MIB session is considered expired when **either** condition is met (`MibLoginF
On detection the client auto-recovers by re-running the login flow using stored credentials, refreshes `xxid` + `nonceGenerator` in the in-flight payload, and retries the original request once. Callers receive the retried response transparently. If the recovery itself hits expiry again it surfaces a `SessionExpiredException`.
### Keep-Alive
```
POST https://faisamobilex-wv-v2.mib.com.mv/aProfile/keepAlive
```
Empty body, session cookies only. Sent periodically to keep the session open. An expired session returns HTTP `419` with:
```json
{ "success": false, "responseCode": "3", "reasonCode": "41", "reasonText": "Session Expired" }
```
### WebView User-Agent
```
@@ -93,11 +119,11 @@ Mozilla/5.0 (Linux; Android {version}; wv) AppleWebKit/537.36 (KHTML, like Gecko
|---|---|---|
| 1 | [01-encryption.md](01-encryption.md) | Blowfish encryption, DH key exchange, nonce computation |
| 2 | [02-login.md](02-login.md) | Device registration and regular login flows |
| 3 | [03-accounts.md](03-accounts.md) | Select profile, account balances |
| 3 | [03-accounts.md](03-accounts.md) | Select profile, account balances, account refresh |
| 4 | [04-history.md](04-history.md) | Transaction history |
| 5 | [05-cards.md](05-cards.md) | Debit card list |
| 6 | [06-financing.md](06-financing.md) | Financing deals |
| 7 | [07-profile.md](07-profile.md) | Personal profile (HTML scrape) |
| 7 | [07-profile.md](07-profile.md) | Personal details, profile image |
| 8 | [08-transfer.md](08-transfer.md) | Account lookup and fund transfer |
| 9 | [09-contacts.md](09-contacts.md) | Beneficiary management |
| 10 | [10-activity-history.md](10-activity-history.md) | Activity / audit log |
-237
View File
@@ -1,237 +0,0 @@
# MIB API v1 to v2 Upgrade Tracking
This document tracks all MIB API calls used by thijooree and their upgrade status from v1 to v2.
---
## Summary
**Total API Endpoints:** 28+
**Categories:** 8
**Last Updated:** 2026-10-22
---
## API Endpoints by Category
### 1. Authentication & Login (Encrypted API)
| API Code | Endpoint | Purpose | Used By | Status |
|----------|----------|---------|---------|--------|
| `sfunc=r` + `S40` | Key Exchange (Init) | First-time device registration | `MibLoginFlow.kt` | ✅ |
| `sfunc=i` + `S40` | Key Exchange (Login) | Regular login session setup | `MibLoginFlow.kt` | ✅ |
| `sfunc=n` + `C41` | Registration Init | Initialize device registration flow | `MibLoginFlow.kt` | ✅ |
| `sfunc=n` + `C42` | OTP Verification | Verify one-time password after registration | `MibLoginFlow.kt` | ✅ |
| `sfunc=n` + `A41` | Main Login | Submit username/password credentials | `MibLoginFlow.kt` | ✅ |
| `sfunc=n` + `A44` | Get User Salt | Retrieve user salt for password hashing | `MibLoginFlow.kt` | ✅ |
| `sfunc=n` + `P47` | Switch Profile | Activate different user profile | `MibLoginFlow.kt` | ✅ |
| `sfunc=n` + `A80` | Accounts | Refresh account list/balances for active profile (same shape as P47 `accountBalance`) | — (not used yet) | ℹ️ |
| `sfunc=n` + `A83` | GIA Accounts | `{start:1,end:30,statusType:0,includeCount:0}` | — (not used) | ℹ️ |
| `sfunc=n` + `A47` | Pending approvals | `{status:"P",start:0,end:30,includeCount:1}` (business profiles) | — (not used) | ℹ️ |
| `sfunc=n` + `P41` | Profile image | `{imageHash}` → base64 JPEG in `profileImage` | — (not used yet) | ℹ️ |
| `sfunc=n` + `B46` | Beneficiary image | `{imageHash}` → base64 JPEG in `profileImage` | — (not used yet) | ℹ️ |
| `sfunc=i` + `P80` | Promo banners | Encrypted with **key1**, not session key | — (not used) | ℹ️ |
---
### 2. Beneficiary & Contact Management
v2 moved all contact calls to the encrypted API (`sfunc=n`). Bank IDs/names/SWIFT come from `B49` (login).
| v1 endpoint | v2 route | Request | Used By | Status |
|-------------|----------|---------|---------|--------|
| `/ajaxBeneficiary/getCategories` | `B41` | Categories come back in every `B41` response as `categories[]` — no separate call | `MibContactsClient.kt`, `HomeActivity.kt` | 🔧 implemented — needs device test |
| `/ajaxBeneficiary/main` | `B41` | `{start,end,benefType:"A",search:"",includeCount:1,searchCategoryId:0,sortBenef:""}` — selects 1-based `rnum` between start/end inclusive; last row has `last:"1"` | `MibContactsClient.kt`, `AddContactSheetFragment.kt` | 🔧 implemented — needs device test |
| `/ajaxBeneficiary/createLocalBeneficiary` | `B43` | `{benefName,benefAccount,bankNo,nickName,transferCy,categoryId,image?,bankName,bankSwift}` — nickname > ~20 chars → `invalid nick name` | `MibContactsClient.kt`, `AddContactSheetFragment.kt` | 🔧 implemented (BML captured; MIB-internal bankName/swift from B49, not captured) |
| — (new) | `B52` | Update: B43 fields + `benefNo`, `imageChanged:"0"/"1"`, `image` when changed | `MibContactsClient.kt`, `AddContactSheetFragment.kt` (edit mode), `ContactDetailsSheetFragment.kt` | 🔧 implemented — needs device test |
| `/ajaxBeneficiary/deleteBeneficiary` | `B48` | `{benefNo}` | `MibContactsClient.kt`, `ContactManager.kt` | 🔧 implemented — needs device test |
| `/ajaxBeneficiary/getProfileImage` | `B46` | `{imageHash}` → `profileImage` base64 JPEG | `MibContactsClient.kt` + image loaders | 🔧 implemented — needs device test |
| — | `B40` | Account name lookup `{search}` → `[{benefName,benefAccount,bankCode,benefBankName}]` | — (check against transfer lookups) | ℹ️ |
| — | `T63` | Currencies + rates | — (not used) | ℹ️ |
----------|---------|---------|--------|
| `/ajaxBeneficiary/getCategories` | Fetch beneficiary category list | `MibContactsClient.kt` | ☐ |
| `/ajaxBeneficiary/main` | List all contacts (paginated) | `MibContactsClient.kt`, `AddContactSheetFragment.kt` | ☐ |
| `/ajaxBeneficiary/createLocalBeneficiary` | Add new beneficiary | `MibContactsClient.kt`, `AddContactSheetFragment.kt` | ☐ |
| `/ajaxBeneficiary/deleteBeneficiary` | Remove beneficiary | `MibContactsClient.kt`, `ContactsFragment.kt` | ☐ |
| `/ajaxBeneficiary/getProfileImage` | Get contact profile picture | `MibContactsClient.kt` | ☐ |
---
### 3. Fund Transfers
v2 moved transfers and all destination lookups to the encrypted API (`sfunc=n`). Failures come back as HTTP 400 with an encrypted body.
| v1 endpoint | v2 route | Request / notes | Used By | Status |
|-------------|----------|-----------------|---------|--------|
| `/ajaxTransfer/transferInternal` | `T50` | `{benefName,benefNo:"0",fromAccountNo,benefAccountNo,transferCy,benefCurrencyCode,amount,bankNo:"2",purpose,otp,otpType:"3"}` → `data[0].{date,trxId,availableBalance}` + `accountBalance[]` | `MibTransferClient.kt`, `MibTransferHandler.kt` | 🔧 implemented — needs device test |
| `/ajaxTransfer/transferLocal` | `T51` | Same as T50 with `bankNo:"3"` → `data[0].{date,trxId}` ("Local transfer completed successfully via Favara") | `MibTransferClient.kt`, `MibTransferHandler.kt` | 🔧 implemented — needs device test |
| `/ajaxBeneficiary/getAccountName` | `B40` | `{search}` → `data[0].{benefName,benefAccount,bankCode,benefBankName,transferCy?,transferCyDesc?}`; `bankCode` is `"MIB"` or a BIC (`MADVMVMV`, `MALBMVMV`); empty `data` = not found | `MibTransferClient.kt` | 🔧 implemented — needs device test |
| `/AjaxAlias/getIPSAccount` | `B40` | as above | `MibTransferClient.kt` | 🔧 implemented — needs device test |
| `/AjaxAlias/getAlias` | `B40` | as above; phone must be bare 7 digits (`+960 …` returns empty); Favara ID needs uppercase `A` (`a239225` fails) | `MibTransferClient.kt` | 🔧 implemented — needs device test |
| — | `T71` / `T66` / `T63` / `T70` | AML limits / USD sell rate / currencies / upload settings | — (not used) | ℹ️ |
**Transfer errors seen:**
- `reasonCode 213` "Invalid OTP" (`data[0].otpId`) and `reasonCode 707` "That OTP is already used!" → thijooree waits for the next TOTP window and retries once, then shows the error.
- `reasonCode 201` "Transaction currency does not match From Account currency!"
----------|---------|---------|--------|
| `/ajaxTransfer/transferInternal` | MIB-to-MIB internal transfer (Bank code 2) | `MibTransferClient.kt`, `TransferFragment.kt` | ☐ |
| `/ajaxTransfer/transferLocal` | Transfer to BML account (Bank code 3) | `MibTransferClient.kt`, `TransferFragment.kt` | ☐ |
| `/ajaxBeneficiary/getAccountName` | Lookup MIB account (17-digit format) | `MibTransferClient.kt`, `MibTransferHandler.kt` | ☐ |
| `/AjaxAlias/getIPSAccount` | Lookup IPS/BML account (15-digit format) | `MibTransferClient.kt`, `MibTransferHandler.kt` | ☐ |
| `/AjaxAlias/getAlias` | Lookup Favara alias (shortcodes, IDs, email) | `MibTransferClient.kt`, `MibTransferHandler.kt` | ☐ |
---
### 4. Debit Card Operations
| Endpoint | Purpose | Used By | Status |
|----------|---------|---------|--------|
| `/ajaxDebitCard/fetchCardInfos` | Retrieve list of debit cards | `MibCardsClient.kt`, `PayWithCardFragment.kt` | ✅ host switched, tested |
| `/ajaxDebitCard/freeze` | Freeze/block debit card | `MibCardsClient.kt` | ✅ host switched, tested |
| `/ajaxDebitCard/unfreeze` | Unfreeze/unblock debit card | `MibCardsClient.kt` | ✅ host switched, tested |
---
### 5. Transaction History
| Endpoint | Purpose | Used By | Status |
|----------|---------|---------|--------|
| ~~`/ajaxAccounts/trxHistory`~~ → `sfunc=n` + `A84` | Fetch account transaction history (paginated). v2 moved this to the encrypted API: `{accountNo,start:0,end:19,includeCount:true,trxType:0,sortTrx:"date",sortDir:"desc"}` (0-based inclusive), response fields unchanged, `total_count` present | `MibHistoryClient.kt`, `HistoryFetcher.kt`, `TransferHistoryFragment.kt` | ✅ implemented, tested incl. paging |
---
### 6. Activity & Audit Logs
| Endpoint | Purpose | Used By | Status |
|----------|---------|---------|--------|
| `/aProfile/getPagedActivityHistory` | Fetch activity alerts and audit logs (paginated) | `MibActivityHistoryClient.kt` | ☐ |
---
### 7. User Profile
| v1 endpoint | v2 source | Notes | Used By | Status |
|-------------|-----------|-------|---------|--------|
| `GET /personalProfile` (HTML scrape) | `A41` login response | `fullName`, `email` | `MibLoginFlow.kt` | 🔧 implemented — needs device test |
| — | `POST /ajaxAlias/getAliasTypes` (`-wv-v2`, empty body) | `data[].{id,aliasTypeName,suggestedAlias}`: `0` mobile, `1` email, `3` National ID. Tied to the active profile, so the personal profile (`profileType 0`) is selected first. Fetched at login and cached. | `MibProfileClient.kt`, `SettingsLoginsFragment.kt` | 🔧 implemented — needs device test |
| — | `POST /ajaxAlias/main` | Registered Favara aliases (`AlsBfy`, `CdtrAcct`, `AdrLine`, …) | — (not used) | ℹ️ |
----------|---------|---------|--------|
| `GET /personalProfile` | HTML scrape: fullName, username, email, mobile, enrolled date | `MibProfileClient.kt` | ☐ |
---
### 8. Financing & Loans
| Endpoint | Purpose | Used By | Status |
|----------|---------|---------|--------|
| `GET /financing?dashurl=1` | Fetch financing/loan deals (HTML parsing) | `MibFinancingClient.kt` | ✅ works on `-wv-v2` |
---
### 9. Session Management
| Endpoint | Purpose | Used By | Status |
|----------|---------|---------|--------|
| `POST /aProfile/keepAlive` | Periodic session keep-alive (empty body) | `HomeActivity.kt` | 🔧 host switched (seen on `-wv-v2` in HAR, 419 after expiry) — needs device test |
---
## Implementation Files Reference
### Core MIB API Clients
- `app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt` - Authentication
- `app/src/main/java/sh/sar/basedbank/api/mib/MibTransferClient.kt` - Transfers & account lookup
- `app/src/main/java/sh/sar/basedbank/api/mib/MibContactsClient.kt` - Contacts/beneficiaries
- `app/src/main/java/sh/sar/basedbank/api/mib/MibCardsClient.kt` - Card operations
- `app/src/main/java/sh/sar/basedbank/api/mib/MibHistoryClient.kt` - Transaction history
- `app/src/main/java/sh/sar/basedbank/api/mib/MibActivityHistoryClient.kt` - Activity logs
- `app/src/main/java/sh/sar/basedbank/api/mib/MibProfileClient.kt` - User profile
- `app/src/main/java/sh/sar/basedbank/api/mib/MibFinancingClient.kt` - Financing
### Security & Utilities
- `app/src/main/java/sh/sar/basedbank/api/mib/MibCrypto.kt` - AES encryption/decryption
- `app/src/main/java/sh/sar/basedbank/api/mib/MibNonce.kt` - Nonce generation (anti-replay)
- `app/src/main/java/sh/sar/basedbank/api/mib/MibModels.kt` - Data models
- `app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt` - Credential storage
### Parsers & Handlers
- `app/src/main/java/sh/sar/basedbank/util/mibapi/MibHistoryParser.kt`
- `app/src/main/java/sh/sar/basedbank/util/mibapi/MibAccountParser.kt`
- `app/src/main/java/sh/sar/basedbank/util/mibapi/MibContactParser.kt`
- `app/src/main/java/sh/sar/basedbank/ui/home/transfer/MibTransferHandler.kt`
---
## Base URLs
| Type | URL |
|------|-----|
| **Encrypted API** | `https://faisamobilex-smvc-v2.mib.com.mv/index/` |
| **WebView/Data** | `https://faisamobilex-wv-v2.mib.com.mv` (v1: `faisamobilex-wv`) |
---
## Authentication & Security
### Session Headers
```
Cookie: mbmodel=AND-1.0; xxid=<session_id>; IBSID=<session_id>; mbnonce=<nonce>; time-tracker=597
X-Requested-With: XMLHttpRequest
Accept: */*
Origin: https://faisamobilex-wv-v2.mib.com.mv
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
```
### Session Expiry Detection
- HTTP `419` status code
- JSON `reasonCode == "505"` in encrypted response
- Auto-recovery: re-login and retry (once)
### Password Hashing (PGF03)
```
pgf03 = SHA256(clientSalt + SHA256(SHA256(password) + userSalt))
```
---
## Upgrade Checklist
### Core Implementation
- [x] Update encrypted API endpoint URL
- [x] Update User-Agent header
- [x] Add X-Request-ID header
- [x] Verify Blowfish encryption compatibility
- [x] Test device registration flow
- [x] Test regular login flow
- [x] Test OTP verification
- [x] Test profile switching
### WebView Endpoints (Pending)
- [x] Switch all WebView clients to `faisamobilex-wv-v2` host and `mbmodel=AND-1.0` cookie
- [x] Capture beneficiary management — moved to encrypted `B4x`/`B52`, implemented
- [ ] Test contacts on device (list, add with/without photo, edit, delete, images)
- [x] Capture transfer endpoints — moved to encrypted `T50`/`T51`/`B40`, implemented with OTP retry
- [ ] Test transfers on device (MIB→MIB, MIB→BML, lookups incl. Favara alias)
- [x] Capture debit card endpoints (`/ajaxDebitCard/*`) — unchanged apart from host
- [x] Test debit card endpoints on device
- [x] Capture transaction history — moved to encrypted `A84`, implemented
- [x] Test transaction history on device (incl. page 2)
- [ ] Test activity history (`/aProfile/getPagedActivityHistory`)
- [x] Capture user profile — `/personalProfile` scrape replaced by A41 + `getAliasTypes`
- [ ] Test login details (name, email, mobile, ID card) on device
- [x] Test financing (`/financing?dashurl=1`) — works on `-wv-v2`
- [ ] Test session keep-alive (`/aProfile/keepAlive`)
### Validation
- [ ] Verify all account balances display correctly
- [ ] Verify error handling (419, 505) and auto-recovery
- [ ] Verify all data formats match expectations
- [ ] Full end-to-end user flow test
---
**Implementation:** 2026-10-22 ✅
**WebView Testing:** In progress