From a5124096d7656721427c9c84777cb8bcc42218f0 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Sat, 30 May 2026 19:00:57 +0500 Subject: [PATCH] update docs --- README.md | 6 +- docs/README.md | 21 ++ docs/thijooree/01-transfer-flows.md | 247 ++++++++++++++++++++++ docs/{ => thijooree}/AI_SECURITY_CHECK.md | 0 docs/{ => thijooree}/PARSERS.md | 0 docs/thijooree/README.md | 11 + 6 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 docs/README.md create mode 100644 docs/thijooree/01-transfer-flows.md rename docs/{ => thijooree}/AI_SECURITY_CHECK.md (100%) rename docs/{ => thijooree}/PARSERS.md (100%) create mode 100644 docs/thijooree/README.md diff --git a/README.md b/README.md index e9b5448..4c34848 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,11 @@ A native Android client for Maldivian banking services. It is a pure client: req ## Privacy -No data ever leaves your device except the API calls to the banking services themselves. See the [security audit](docs/AI_SECURITY_CHECK.md) for a full list of every server the app connects to. +No data ever leaves your device except the API calls to the banking services themselves. See the [security audit](docs/thijooree/AI_SECURITY_CHECK.md) for a full list of every server the app connects to. + +## Documentation + +API reverse-engineering notes and app internals are in [`docs/`](docs/README.md). ## Disclaimer diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f480427 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +# Thijooree Documentation + +--- + +## App Internals + +| Section | Description | +|---|---| +| [thijooree/](thijooree/README.md) | UI flows, routing logic, parsers, and security audit for the Android client | + +--- + +## Bank & Service APIs + +| Section | Description | +|---|---| +| [bmlapi/](bmlapi/README.md) | Bank of Maldives — hybrid web/OAuth login, dashboard, transfers, cards, QR payments, tap-to-pay | +| [mibapi/](mibapi/README.md) | MIB Faisanet — Blowfish-encrypted API + WebView session, accounts, transfers, contacts | +| [fahipayapi/](fahipayapi/README.md) | Fahipay digital wallet — login, balance, history, contacts | +| [dhiraaguapi/](dhiraaguapi/README.md) | Dhiraagu Easy Pay — number lookup for reload / bill pay | +| [ooredooapi/](ooredooapi/README.md) | Ooredoo Quick Pay — number validation for Raastas / bill pay | diff --git a/docs/thijooree/01-transfer-flows.md b/docs/thijooree/01-transfer-flows.md new file mode 100644 index 0000000..a4bb464 --- /dev/null +++ b/docs/thijooree/01-transfer-flows.md @@ -0,0 +1,247 @@ +# Transfer Flows + +The transfer screen (`TransferFragment`) handles all outgoing payments across MIB, BML, and Fahipay. This document covers how the UI routes transfers, how recipients are looked up, which combinations are allowed, and which are rejected. + +--- + +## Entry Points + +`TransferFragment` can be launched in several modes depending on context: + +| Factory method | Behaviour | +|---|---| +| `newInstance(account, name, ...)` | Pre-fills the "To" card from a contact or recents pick | +| `newInstanceFrom(account)` | Pre-selects the given account in the "From" dropdown | +| `newInstanceFromQr(account, name, amount, remarks)` | Pre-fills recipient + optional amount/remarks from a PayMV QR scan | +| `newInstanceFromBmlQr(qrUrl, fromAccountNumber?)` | BML card/gateway QR merchant payment mode — locks recipient, may pre-fill amount | +| `newInstanceWithAutoScan()` | Opens the QR scanner immediately on load | + +--- + +## Account Input Detection + +The raw "To" field input is normalised first (spaces stripped, `+960`/`960` country prefix removed if the result is 7 digits), then classified: + +| Pattern | Type | +|---|---| +| Starts with `9`, exactly 17 digits | `MIB_ACCOUNT` | +| Starts with `7`, exactly 13 digits | `BML_ACCOUNT` | +| Starts with `7` or `9`, exactly 7 digits | `PHONE` | +| Starts with `A` followed by 6 digits | `NATIONAL_ID` | +| Contains `@` | `EMAIL` | +| Anything else | `UNKNOWN` | + +--- + +## Recipient Lookup + +Lookup behaviour depends on the **source account's bank**. + +### Fahipay source + +Only `PHONE` input is accepted. Any other type is rejected immediately with an error on the "To" field. + +Phone lookup hits both Dhiraagu and Ooredoo in parallel (order depends on the first digit): + +- Numbers starting with `7`: Dhiraagu first, Ooredoo fallback +- Numbers starting with `9`: Ooredoo first, Dhiraagu fallback + +The result maps to one or more Fahipay services: + +| Carrier result | Service shown | +|---|---| +| Dhiraagu `RELOAD` | Dhiraagu Reload | +| Dhiraagu `BILL_PAY` | Dhiraagu Bill Pay | +| Ooredoo `PRE` or `HYBRID` | Raastas (prepaid top-up) | +| Ooredoo `POST` or `HYBRID` | Ooredoo Bill Pay | + +If exactly one service matches, it is auto-selected. If multiple match (Ooredoo `HYBRID` gives two), a chip group is shown for the user to choose. + +### BML source + +1. If the input type is `MIB_ACCOUNT`, calls `BmlValidateClient.verifyMibAccount()`. +2. Otherwise calls `BmlValidateClient.validateAccount()`. +3. If either BML call fails and a MIB session is available, falls back to `MibTransferClient.lookup()`. +4. If both fail, shows the error from the MIB lookup (or a generic "account not found"). + +There is also a short-circuit: if the input matches a saved contact whose `transferCyDesc` is not `MVR`, the contact is used directly without a network lookup. + +### MIB source + +Calls `MibTransferClient.lookup()` directly. Errors from `MibLookupException` are shown verbatim to the user. + +### BML-only session (no MIB session) + +Falls back to `BmlValidateClient.validateAccount()` only. + +--- + +## Transfer Type Routing + +Once the source and destination are resolved, the transfer type is determined as follows. This applies for both BML personal (`doBmlTransfer`) and BML business (`startBmlBusinessOtpFlow`) — the routing logic is identical. + +``` +Source: BML + +├── isSrcCard (BML_PREPAID / BML_CREDIT / BML_DEBIT) +│ └── type = CAD creditAccount = dest BML CASA internalId (or dest account number) +│ +├── isDestMyCard (destination is user's own BML card) +│ └── type = CPA creditAccount = card internalId +│ +├── isDestMib && currency == MVR +│ └── type = DOT creditAccount = MIB account number bank = "MIB" +│ +├── isDestMib && currency == USD +│ └── Requires a saved BML contact for that MIB account (see Rejections) +│ type = DOT creditAccount = contact.benefNo (numeric) bank = null +│ +└── everything else (BML → BML CASA, BML → other local bank) + └── type = IAT creditAccount = dest account number +``` + +``` +Source: MIB + +├── isDestMib (17-digit 9… account) +│ └── bankNo = 2 endpoint = transferInternal +│ +└── everything else (BML or other local bank) + └── bankNo = 3 endpoint = transferLocal +``` + +``` +Source: Fahipay + +└── Routed to the selected service: + FAHIPAY_TRANSFER, RAASTAS, OOREDOO_BILL, DHIRAAGU_RELOAD, DHIRAAGU_BILL +``` + +--- + +## Rejected Combinations + +These combinations are blocked before a transfer is attempted. + +### BML USD → MIB (no saved contact) + +**Condition:** source is BML, currency is USD, destination is a MIB account, and no BML contact exists for that account number. + +**Result:** dialog shown — "Contact required". The user must first add the MIB account as a BML contact before a USD cross-bank transfer can proceed. + +> This is enforced in `initiateTransfer()` before reaching `doBmlTransfer`. + +--- + +### BML QR payment — non-card source + +**Condition:** in BML QR merchant payment mode and the user selects a non-card account (i.e. not `BML_PREPAID`, `BML_CREDIT`, or `BML_DEBIT`) from the "From" dropdown. + +**Result:** selection is rejected with a toast: "Unsupported for BML QR — select a card". The dropdown resets. + +--- + +### Fahipay — non-phone destination + +**Condition:** source is Fahipay and the input type is anything other than `PHONE`. + +**Result:** inline error on the "To" field: "Only phone numbers are supported for Fahipay transfers." + +--- + +### No source account selected + +**Condition:** user taps the lookup button or the transfer button without selecting a "From" account. + +**Result:** toast: "Please select a source account first." + +--- + +### Inactive BML card as source + +**Condition:** a BML card (`BML_PREPAID`, `BML_CREDIT`, `BML_DEBIT`) with `statusDesc != "Active"` appears in the dropdown but is not selectable — `getAccount()` returns `null` for it and `isEnabled()` returns `false`. + +**Result:** the row is shown at 40% opacity and cannot be tapped. + +--- + +### Missing internalId + +**Condition:** a BML source account has a blank `internalId` (needed as the `debitAccount` in BML API calls). + +**Result:** transfer is aborted with a toast: "Missing internal account ID — please refresh your accounts." + +--- + +## Warnings (allowed but flagged) + +These combinations proceed after user confirmation but show a prominent red warning in the confirm dialog. + +### USD source → MVR destination + +> "You are transferring from a USD account to an MVR account. The currency will be converted at the bank's rate and this cannot be reversed!" + +**Condition:** `src.currencyName == "USD"` and the resolved destination account's currency is `MVR`. + +--- + +### BML credit card as source + +> "Transferring from a credit card is treated as a cash advance. Cash advance fees will be charged on the 10th of the month." + +**Condition:** `src.profileType == "BML_CREDIT"`. + +--- + +## BML Business Profile OTP Flow + +Business profiles use a manual OTP delivered via email or SMS rather than a TOTP seed. The flow replaces the standard single-step confirm: + +1. **Initiate** — `startBmlBusinessOtpFlow()` calls `BmlAccountClient.fetchTransferChannels()` to list available channels (email, SMS). +2. **Channel selection** — a channel picker is shown inline. Transfer fields are locked (dimmed, disabled). +3. **Initiate with channel** — `BmlTransferClient.initiateTransfer()` is called with the chosen channel, which triggers the OTP dispatch. +4. **OTP entry** — an OTP input field appears. The transfer button label changes to "Verify Payment". +5. **Confirm** — `BmlTransferClient.confirmTransfer()` is called with the entered OTP (not a generated TOTP). + +If channel fetch fails or returns empty, the flow is aborted and the form is re-enabled. + +**Profile detection:** `isBusinessProfile()` checks `bmlProfilesMap[loginId]` for a profile entry matching `src.profileId` with `profileType == "business"`. + +--- + +## BML QR Merchant Payment Flow + +Triggered when the transfer screen is opened via `newInstanceFromBmlQr()` or when a BML ebanking/pay.bml URL is scanned from the QR scanner. + +Two sub-modes: + +| Mode | Trigger | Extra step | +|---|---|---| +| Static card QR | URL starts with `https://ebanking.bankofmaldives.com.mv/qrpay/` | None | +| Gateway QR | URL starts with `https://pay.bml.com.mv/app/` | `BmlQrPayClient.preInitiatePayment()` required before initiate | + +Flow: +1. `lookupBmlQrMerchant()` — fetches merchant info via `BmlQrPayClient.lookupPayRequest()`. Locks the "To" row. +2. For dynamic QRs (`info.amount > 0`), pre-fills the amount and locks the amount field. +3. Remarks field is locked (not applicable for merchant payments). +4. On confirm: TOTP is generated, then `initiatePayment()` → (for gateway QR: `preInitiatePayment()` first) → `confirmPayment()` with a fresh TOTP. +5. On success: a success dialog is shown (no receipt saved). Back-press returns to previous screen. + +--- + +## Transfer Button Enable Conditions + +The transfer button is only enabled when all of the following are true: + +- A source account is selected +- A recipient is resolved (`resolvedAccountNumber` not blank, or `bmlQrInfo` is set) +- Amount is greater than `0` +- No connectivity error for `NO_INTERNET` or for the source bank + +--- + +  + +--- + +[Back to app docs index](README.md) diff --git a/docs/AI_SECURITY_CHECK.md b/docs/thijooree/AI_SECURITY_CHECK.md similarity index 100% rename from docs/AI_SECURITY_CHECK.md rename to docs/thijooree/AI_SECURITY_CHECK.md diff --git a/docs/PARSERS.md b/docs/thijooree/PARSERS.md similarity index 100% rename from docs/PARSERS.md rename to docs/thijooree/PARSERS.md diff --git a/docs/thijooree/README.md b/docs/thijooree/README.md new file mode 100644 index 0000000..e842ec1 --- /dev/null +++ b/docs/thijooree/README.md @@ -0,0 +1,11 @@ +# App Internals + +Documentation for app-specific logic — UI flows, routing decisions, and business rules implemented in the Android client. + +--- + +| Document | Description | +|---|---| +| [01 — Transfer Flows](01-transfer-flows.md) | TransferFragment entry points, recipient lookup, transfer type routing, rejected combinations, BML business OTP flow, BML QR merchant payments | +| [Parsers](PARSERS.md) | Account display parser architecture — how raw bank API data is normalised into a unified `AccountListDisplay` model | +| [AI Security Audit](AI_SECURITY_CHECK.md) | Full source security audit — credential storage, network layer, manifest, data privacy |