forked from shihaam/thijooree
update docs
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
@@ -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)
|
||||
@@ -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 |
|
||||
Reference in New Issue
Block a user