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

This commit is contained in:
2026-05-30 19:00:57 +05:00
parent 1d2cd40b3c
commit a5124096d7
6 changed files with 284 additions and 1 deletions
+5 -1
View File
@@ -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
+21
View File
@@ -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 |
+247
View File
@@ -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)
+11
View File
@@ -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 |