diff --git a/docs/mibapi/03-accounts.md b/docs/mibapi/03-accounts.md index 8cc8dd0..855b7aa 100644 --- a/docs/mibapi/03-accounts.md +++ b/docs/mibapi/03-accounts.md @@ -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": "", + "data": { + "nonce": "", + "appId": "", + "sodium": "", + "routePath": "A80", + "xxid": "" + } +} +``` + +**Response**: +```json +{ + "success": true, + "responseCode": "1", + "reasonCode": "101", + "reasonText": "Accounts retrieval success!", + "data": [ ... ], + "beneficiaryVersion": 0, + "menuVersion": "" +} +``` + +`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`: diff --git a/docs/mibapi/04-history.md b/docs/mibapi/04-history.md index 86e5ef2..1a64061 100644 --- a/docs/mibapi/04-history.md +++ b/docs/mibapi/04-history.md @@ -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": "", + "data": { + "accountNo": "90101000000001000", + "start": 0, + "end": 19, + "includeCount": true, + "trxType": 0, + "sortTrx": "date", + "sortDir": "desc", + "nonce": "", + "appId": "", + "sodium": "", + "routePath": "A84", + "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= -``` - ---- - -## 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": "", + "descr3": "
", + "remarks": "
", + "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. --- diff --git a/docs/mibapi/05-cards.md b/docs/mibapi/05-cards.md index 1e13623..785185d 100644 --- a/docs/mibapi/05-cards.md +++ b/docs/mibapi/05-cards.md @@ -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=&dashurl=1 +Referer: https://faisamobilex-wv-v2.mib.com.mv/debitCards/manage?cardId= ``` ### Request Body (form-urlencoded) @@ -106,16 +113,22 @@ Referer: https://faisamobilex-wv.mib.com.mv//debitCards/manage?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) | --- diff --git a/docs/mibapi/06-financing.md b/docs/mibapi/06-financing.md index 9f5fe5a..5ab8bca 100644 --- a/docs/mibapi/06-financing.md +++ b/docs/mibapi/06-financing.md @@ -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 ``` --- diff --git a/docs/mibapi/07-profile.md b/docs/mibapi/07-profile.md index 8ee633b..b93169c 100644 --- a/docs/mibapi/07-profile.md +++ b/docs/mibapi/07-profile.md @@ -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": "", + "roleName": "Consumer Premium" +} ``` -### Authentication - -Session cookies only — no additional AJAX headers required. +### Alias Suggestions — `getAliasTypes` ``` -Cookie: mbmodel=IOS-1.0; xxid=; IBSID=; mbnonce=; 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 `
` with the user's full name and `` elements with labelled fields. - -### Parsing Strategy - -**Full name** — extracted from: -```html -
Mohamed Ali
+``` +Referer: https://faisamobilex-wv-v2.mib.com.mv/Alias/Alias?dashurl=1 ``` -Regex: -```kotlin -Regex("""
\s*([^<]+)\s*
""") +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 -Username:...myusername -``` - -Regex (used for each label): -```kotlin -Regex( - """]*>\s*]*>\s*$label\s*]*>.*?]*>([^<]+)""", - 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 `
`: +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": "
", + "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` diff --git a/docs/mibapi/08-transfer.md b/docs/mibapi/08-transfer.md index 7c61d61..8ca0355 100644 --- a/docs/mibapi/08-transfer.md +++ b/docs/mibapi/08-transfer.md @@ -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=` - -**Response**: -```json -{ - "success": true, - "responseCode": "2", + "sfunc": "n", + "xxid": "", "data": { - "BfyNm": "Account Holder Name", - "CdtrAcct": { - "Acct": "90100000000000000", - "FinInstnId": "MADVMVMV" - } + "search": "7700000000000", + "nonce": "", + "appId": "", + "sodium": "", + "routePath": "B40", + "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": "", + "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": "", + "appId": "", + "sodium": "", + "routePath": "T51", + "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. --- diff --git a/docs/mibapi/09-contacts.md b/docs/mibapi/09-contacts.md index 5a879fa..5f52d27 100644 --- a/docs/mibapi/09-contacts.md +++ b/docs/mibapi/09-contacts.md @@ -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": "", + "data": { + "start": 1, + "end": 10, + "benefType": "A", + "search": "", + "includeCount": 1, + "searchCategoryId": 0, + "sortBenef": "", + "nonce": "", + "appId": "", + "sodium": "", + "routePath": "B41", + "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": "", + "benefImgHash": "", + "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": "", "routePath": "B46" } ``` -POST https://faisamobilex-wv.mib.com.mv/ajaxBeneficiary/getProfileImage -``` - -Body: `imageHash=` ### Response ```json { "success": true, + "msg": "image found", + "message": "image found", "profileImage": "" } ``` @@ -135,98 +163,125 @@ Body: `imageHash=` --- -## Create Contact +## Create Contact — `routePath: B43` +**Request** — route fields: +```json +{ + "benefName": "RECIPIENT NAME", + "benefAccount": "7700000000001", + "bankNo": "3", + "nickName": "Nickname", + "transferCy": "462", + "categoryId": "0", + "image": "", + "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=` - -### 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": "", ... } ], + "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": "", ... } ], + "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 +} +``` --- diff --git a/docs/mibapi/10-activity-history.md b/docs/mibapi/10-activity-history.md index 572323c..b97df51 100644 --- a/docs/mibapi/10-activity-history.md +++ b/docs/mibapi/10-activity-history.md @@ -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=; IBSID=; mbnonce=; time-tracker=597 +Cookie: mbmodel=AND-1.0; xxid=; IBSID=; mbnonce=; time-tracker=597 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Linux; Android ; wv) AppleWebKit/537.36 ... ``` diff --git a/docs/mibapi/README.md b/docs/mibapi/README.md index 0b2a6cb..db8250e 100644 --- a/docs/mibapi/README.md +++ b/docs/mibapi/README.md @@ -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=&data= +xxid=&sfunc=&data= ``` -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": "", + "appId": "", + "sodium": "", + "routePath": "", + "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=; IBSID=; mbnonce=; time-tracker=597 +Cookie: mbmodel=AND-1.0; xxid=; IBSID=; mbnonce=; 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 | diff --git a/docs/mibapi/upgradev2.md b/docs/mibapi/upgradev2.md deleted file mode 100644 index d021c39..0000000 --- a/docs/mibapi/upgradev2.md +++ /dev/null @@ -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=; IBSID=; mbnonce=; 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