add support for fahipay login and view history
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 2s

This commit is contained in:
2026-05-16 21:31:34 +05:00
parent 99a32dc9ed
commit 7864655a82
29 changed files with 2097 additions and 32 deletions

152
docs/fahipayapi/01-login.md Normal file
View File

@@ -0,0 +1,152 @@
# Login
Authenticate a user with their Fahipay ID card number and password.
---
## Endpoint
```
POST https://fahipay.mv/api/app/login/
```
---
## Request
**Content-Type:** `multipart/form-data`
### Form Fields
| Field | Value | Notes |
|---|---|---|
| `email` | `A123456` | The user's national ID card number (e.g. `A123456`) |
| `password` | `••••••••••••••` | The user's Fahipay password |
| `grant_type` | `auth_id` | Always `auth_id` |
| `lang` | `en` | Always `en` |
| `version` | `2.0.0` | App version string |
| `platform` | `BasedBank` | Client identifier (original app sends `app`) |
| `device[available]` | `true` | See [common device fields](README.md#common-form-fields-device-info) |
| `device[platform]` | `Android` | |
| `device[uuid]` | `a1b2c3d4e5f60718` | Persistent 16-char hex UUID, generated once per install |
| `device[model]` | `22101320I` | `Build.MODEL` |
| `device[manufacturer]` | `Xiaomi` | `Build.MANUFACTURER` |
| `device[isVirtual]` | `false` | |
| `device[serial]` | `unknown` | |
> **Note:** The field name is `email` but the value is the ID card number, not an email address.
---
## curl Example
```bash
curl --request POST \
--url https://fahipay.mv/api/app/login/ \
--compressed \
--header 'accept: application/json' \
--header 'accept-encoding: gzip, deflate, br' \
--header 'connection: keep-alive' \
--header 'user-agent: Mozilla/5.0 (Linux; Android 14; 22101320I Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36' \
--form 'email=A123456' \
--form 'password=your_password' \
--form 'grant_type=auth_id' \
--form 'lang=en' \
--form 'version=2.0.0' \
--form 'platform=BasedBank' \
--form 'device[available]=true' \
--form 'device[platform]=Android' \
--form 'device[uuid]=a1b2c3d4e5f60718' \
--form 'device[model]=22101320I' \
--form 'device[manufacturer]=Xiaomi' \
--form 'device[isVirtual]=false' \
--form 'device[serial]=unknown'
```
---
## Responses
### Success — 2FA required
The user has TOTP two-factor authentication enabled. Proceed to the [OTP step](02-otp.md).
```json
{
"two_factor_required": true,
"two_factor_method": "totp",
"title": "Success",
"msg": "You are now logged in.",
"type": "success"
}
```
| Field | Type | Description |
|---|---|---|
| `two_factor_required` | `bool` | `true` — must call `/api/app/otp/` next |
| `two_factor_method` | `string` | `"totp"` — standard TOTP (RFC 6238) |
| `type` | `string` | `"success"` on success, `"error"` on failure |
The server sets the `__Secure-sess` session cookie on this response. It must be included in all subsequent requests.
---
### Success — No 2FA
The user does not have 2FA enabled. The `authID` is returned directly — no OTP step needed.
```json
{
"two_factor_required": false,
"authID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"title": "Success",
"msg": "You are now logged in.",
"type": "success"
}
```
| Field | Type | Description |
|---|---|---|
| `two_factor_required` | `bool` | `false` — login is complete |
| `authID` | `string` | 40-char hex token; use as `authid` header for all subsequent requests |
---
### Failure
```json
{
"title": "Error",
"msg": "Invalid credentials",
"type": "error"
}
```
`type` is `"error"` and `msg` contains a human-readable reason.
---
## Session Cookie
The `__Secure-sess` cookie is set by the server on the first response and must be sent on every subsequent request. It is a standard HTTP cookie with the `Secure` flag.
```
Set-Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; Path=/; Secure; HttpOnly; SameSite=Strict
```
Store both the cookie value and the `authID` together to represent a persisted session.
---
## Next Steps
- If `two_factor_required` is `true` → proceed to **[OTP / 2FA](02-otp.md)**
- If `two_factor_required` is `false` → skip to **[Profile](03-profile.md)**
---
 
---
[← README](README.md)     **Next →** [OTP / 2FA](02-otp.md)

158
docs/fahipayapi/02-otp.md Normal file
View File

@@ -0,0 +1,158 @@
# OTP / 2FA Verification
Submit a TOTP code to complete login when `two_factor_required` was `true` in the [login response](01-login.md).
---
## Endpoint
```
POST https://fahipay.mv/api/app/otp/
```
---
## Prerequisites
- Completed the [login step](01-login.md) and received `two_factor_required: true`
- The `__Secure-sess` session cookie from the login response must be present
- A valid TOTP code from the user's authenticator app
---
## Request
**Content-Type:** `multipart/form-data`
### Form Fields
| Field | Value | Notes |
|---|---|---|
| `code` | `123456` | 6-digit TOTP code from the user's authenticator app |
| `channel` | `totp` | Always `totp` |
| `action` | `login` | Always `login` for the login flow |
| `grant_type` | `auth_id` | Always `auth_id` |
| `lang` | `en` | Always `en` |
| `version` | `2.0.0` | App version string |
| `platform` | `BasedBank` | Client identifier |
| `device[available]` | `true` | Same device fields as login — must match |
| `device[platform]` | `Android` | |
| `device[uuid]` | `a1b2c3d4e5f60718` | Must be the **same UUID** used in the login request |
| `device[model]` | `22101320I` | |
| `device[manufacturer]` | `Xiaomi` | |
| `device[isVirtual]` | `false` | |
| `device[serial]` | `unknown` | |
> The `device[uuid]` must be identical to the one sent in the login request. The server uses this to tie the OTP challenge to the login attempt.
---
## curl Example
```bash
curl --request POST \
--url https://fahipay.mv/api/app/otp/ \
--compressed \
--header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'accept: application/json' \
--header 'accept-encoding: gzip, deflate, br' \
--header 'connection: keep-alive' \
--header 'user-agent: Mozilla/5.0 (Linux; Android 14; 22101320I Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36' \
--form 'code=123456' \
--form 'channel=totp' \
--form 'action=login' \
--form 'grant_type=auth_id' \
--form 'lang=en' \
--form 'version=2.0.0' \
--form 'platform=BasedBank' \
--form 'device[available]=true' \
--form 'device[platform]=Android' \
--form 'device[uuid]=a1b2c3d4e5f60718' \
--form 'device[model]=22101320I' \
--form 'device[manufacturer]=Xiaomi' \
--form 'device[isVirtual]=false' \
--form 'device[serial]=unknown'
```
---
## Responses
### Success
```json
{
"title": "Success",
"authID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"msg": "Code verification successful",
"type": "success"
}
```
| Field | Type | Description |
|---|---|---|
| `authID` | `string` | 40-char hex token — use as `authid` header for all subsequent requests |
| `type` | `string` | `"success"` |
| `msg` | `string` | Human-readable confirmation |
---
### Failure — Wrong code
```json
{
"title": "Error",
"msg": "Invalid OTP code",
"type": "error"
}
```
---
### Failure — Expired / session mismatch
```json
{
"title": "Error",
"msg": "Session expired. Please login again.",
"type": "error"
}
```
If the session cookie has expired or the UUID does not match, re-run the full login flow from [Step 1](01-login.md).
---
## TOTP Details
Fahipay uses standard RFC 6238 TOTP:
| Parameter | Value |
|---|---|
| Algorithm | HMAC-SHA1 |
| Period | 30 seconds |
| Digits | 6 |
| Encoding | Base32 secret |
The user's TOTP seed is set up during initial Fahipay account creation and is the same secret used in any standard authenticator app (Google Authenticator, Aegis, etc.).
---
## Storing the Session
After receiving `authID`, persist both values for future sessions:
| Value | Description |
|---|---|
| `authID` | 40-char hex token — send as `authid` header |
| `__Secure-sess` | Cookie value — send as `Cookie: __Secure-sess=<value>` |
On app restart, attempt requests with the stored session before falling back to a full re-login.
---
&nbsp;
---
[← Login](01-login.md) &nbsp;&nbsp;&nbsp; **Next →** [Profile](03-profile.md)

View File

@@ -0,0 +1,229 @@
# User Profile
Fetch the authenticated user's full profile, including personal details, linked bank accounts, wallet settings, and permissions.
---
## Endpoint
```
GET https://fahipay.mv/actions/getprofile/?lang=en
```
---
## Prerequisites
- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md)
- Valid `__Secure-sess` session cookie
---
## Request
### Headers
| Header | Value |
|---|---|
| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
| `content-type` | `multipart/form-data` |
| `User-Agent` | `okhttp/4.12.0` |
| `Accept-Encoding` | `gzip` |
| `Connection` | `Keep-Alive` |
| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
---
## curl Example
```bash
curl --request GET \
--url 'https://fahipay.mv/actions/getprofile/?lang=en' \
--compressed \
--header 'Accept-Encoding: gzip' \
--header 'Connection: Keep-Alive' \
--header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'User-Agent: okhttp/4.12.0' \
--header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'content-type: multipart/form-data'
```
---
## Response
```json
{
"nidexpiry": "2027-06-01",
"about": "",
"email": "user@example.com",
"country": "Maldives",
"fullname": "Mohamed Ali",
"postcode": "",
"props": {
"accs": {
"bml": {
"mvr": "7730000000001",
"usd": "7730000000002",
"mvr2": "7770000000003"
},
"cbm": {
"mvr": "1000000001"
},
"mib": {
"mvr": "90101000000001000",
"usd": "90101000000002000"
},
"sbi": {
"mvr": "12600000000001"
}
},
"lang": "en",
"wsize": 5000,
"withdraw": {
"fee": "3",
"unit": "%",
"freelimit": 0
},
"IDverified": 1,
"thresholds": {
"amount": 0,
"countries": {
"list": [],
"mode": "deny"
},
"frequency": 0
},
"amountTopup": 1,
"receiptTopup": 1,
"verifiedWith": "efaas",
"notifications": {
"txn": ["push"],
"login": ["push", "email"],
"balance": ["push"]
},
"allowedActions": [
"withdraw",
"payment",
"topup",
"transfer"
],
"two_factor_enabled": 1,
"efaas_login_enabled": 0,
"acc": "500000000001",
"walletType": "basic"
},
"mobile": "9600000001",
"city": "101",
"nid": "A123456",
"level": "1",
"address": "Example Address",
"accs": {
"bml": [
{ "name": "MOHAMED ALI" }
]
},
"invitecode": "XXXXX",
"profileID": "0000",
"verificationUploadMethods": ["camera", "file"],
"faceVerificationRequired": true,
"p2pqr": "https://fahipay.mv/api/qrcode/?data=9600000001",
"smsAuth": "xxxxxxxxxxxxxxxxxxxx",
"type": "success"
}
```
---
## Key Fields
### Top-level
| Field | Type | Description |
|---|---|---|
| `fullname` | `string` | User's full name |
| `email` | `string` | Registered email address |
| `mobile` | `string` | Registered mobile number |
| `nid` | `string` | National ID card number (e.g. `A239225`) |
| `nidexpiry` | `string` | NID expiry date (`YYYY-MM-DD`) |
| `profileID` | `string` | Fahipay internal numeric user ID — use in `loginTag` |
| `level` | `string` | Account verification level |
| `country` | `string` | Registered country |
| `city` | `string` | City code |
| `address` | `string` | Street address |
| `invitecode` | `string` | Referral invite code |
| `p2pqr` | `string` | URL to this user's P2P QR code image |
| `type` | `string` | `"success"` or `"error"` |
---
### `props` Object
| Field | Type | Description |
|---|---|---|
| `acc` | `string` | The user's Fahipay wallet account number |
| `walletType` | `string` | `"basic"` or `"premium"` |
| `wsize` | `number` | Wallet size / transaction limit |
| `two_factor_enabled` | `number` | `1` if TOTP 2FA is active |
| `efaas_login_enabled` | `number` | `1` if eFaas login is enabled |
| `IDverified` | `number` | `1` if identity is verified |
| `allowedActions` | `string[]` | Permitted operations: `withdraw`, `payment`, `topup`, `transfer` |
> `props.acc` is the wallet account number shown in the app and used as the primary account identifier.
---
### `props.accs` — Linked Bank Accounts
Contains the user's bank accounts linked to Fahipay, organised by bank code. Used when topping up or withdrawing via linked banks.
| Key | Bank |
|---|---|
| `bml` | Bank of Maldives |
| `mib` | Maldives Islamic Bank |
| `cbm` | Central Bank of Maldives |
| `sbi` | State Bank of India |
Each bank entry is an object of named account numbers:
```json
"bml": {
"mvr": "7730000145458",
"usd": "7730000199959",
"mvr2": "7770000045775"
}
```
Store the raw JSON of `props.accs` — it is needed to determine the source account when initiating top-ups or withdrawals.
---
### `props.withdraw`
| Field | Description |
|---|---|
| `fee` | Withdrawal fee amount |
| `unit` | Fee unit — `%` = percentage, otherwise fixed |
| `freelimit` | Free withdrawal limit (0 = no free limit) |
---
## Error Response
```json
{
"title": "Error",
"msg": "Unauthorized",
"type": "error"
}
```
If the `authID` is invalid or expired, re-run the full [login flow](01-login.md).
---
&nbsp;
---
[← OTP / 2FA](02-otp.md) &nbsp;&nbsp;&nbsp; **Next →** [Balance](04-balance.md)

View File

@@ -0,0 +1,109 @@
# Wallet Balance
Fetch the current balance of the authenticated user's Fahipay wallet.
---
## Endpoint
```
GET https://fahipay.mv/actions/getbalance/?lang=en
```
---
## Prerequisites
- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md)
- Valid `__Secure-sess` session cookie
---
## Request
### Headers
| Header | Value |
|---|---|
| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
| `content-type` | `multipart/form-data` |
| `User-Agent` | `okhttp/4.12.0` |
| `Accept-Encoding` | `gzip` |
| `Connection` | `Keep-Alive` |
| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
### Query Parameters
| Parameter | Value | Description |
|---|---|---|
| `lang` | `en` | Language — always `en` |
---
## curl Example
```bash
curl --request GET \
--url 'https://fahipay.mv/actions/getbalance/?lang=en' \
--compressed \
--header 'Accept-Encoding: gzip' \
--header 'Connection: Keep-Alive' \
--header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'User-Agent: okhttp/4.12.0' \
--header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'content-type: multipart/form-data'
```
---
## Response
### Success
```json
{
"balance": 1.01,
"rewards": "0",
"error": false,
"type": "success"
}
```
| Field | Type | Description |
|---|---|---|
| `balance` | `number` | Current wallet balance in MVR |
| `rewards` | `string` | Rewards/cashback points balance |
| `error` | `bool` | `false` on success |
| `type` | `string` | `"success"` |
> All Fahipay wallet balances are in **MVR** (Maldivian Rufiyaa). There is no multi-currency wallet.
---
### Error
```json
{
"error": true,
"type": "error",
"msg": "Unauthorized"
}
```
If `error` is `true` or `type` is `"error"`, the session is invalid. Re-run the [login flow](01-login.md).
---
## Notes
- This endpoint only returns the Fahipay wallet balance, not any linked bank account balances.
- The `rewards` field is returned as a string even though it represents a numeric value. Parse it with `toDoubleOrNull()`.
- Call this endpoint after [fetching the profile](03-profile.md) to construct the full account object with both account number and balance.
---
&nbsp;
---
[← Profile](03-profile.md) &nbsp;&nbsp;&nbsp; **Next →** [Transaction History](05-history.md)

View File

@@ -0,0 +1,250 @@
# Transaction History
Fetch the user's paginated wallet activity log. Each entry represents a single transaction: top-up, payment, transfer, or withdrawal.
---
## Endpoint
```
GET https://fahipay.mv/actions/activity/?s={start}&l={limit}&lang=en
```
---
## Prerequisites
- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md)
- Valid `__Secure-sess` session cookie
---
## Request
### Headers
| Header | Value |
|---|---|
| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
| `content-type` | `multipart/form-data` |
| `User-Agent` | `okhttp/4.12.0` |
| `Accept-Encoding` | `gzip` |
| `Connection` | `Keep-Alive` |
| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
### Query Parameters
| Parameter | Description | Example |
|---|---|---|
| `s` | Start offset (0-based) | `0`, `15`, `30` |
| `l` | Number of entries to return per page | `15` |
| `lang` | Language | `en` |
---
## Pagination
The API uses offset-based pagination via the `s` (start) and `l` (limit) parameters.
| Page | URL |
|---|---|
| First | `/actions/activity/?s=0&l=15&lang=en` |
| Second | `/actions/activity/?s=15&l=15&lang=en` |
| Third | `/actions/activity/?s=30&l=15&lang=en` |
| N-th | `/actions/activity/?s={(N-1)*15}&l=15&lang=en` |
The response includes a `total` count and a `next` URL. Stop fetching when:
- The returned `entries` array is empty, **or**
- `s + entries.length >= total`
---
## curl Examples
### Page 1
```bash
curl --request GET \
--url 'https://fahipay.mv/actions/activity/?s=0&l=15&lang=en' \
--compressed \
--header 'Accept-Encoding: gzip' \
--header 'Connection: Keep-Alive' \
--header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'User-Agent: okhttp/4.12.0' \
--header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'content-type: multipart/form-data'
```
### Page 2
```bash
curl --request GET \
--url 'https://fahipay.mv/actions/activity/?s=15&l=15&lang=en' \
--compressed \
--header 'Accept-Encoding: gzip' \
--header 'Connection: Keep-Alive' \
--header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'User-Agent: okhttp/4.12.0' \
--header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'content-type: multipart/form-data'
```
---
## Response
```json
{
"entries": [
{
"date": "2026-05-16 15:10:25",
"name": "Cash Deposit",
"details": "Transferred Via BML ebanking",
"icon": "https://fahipay.mv/images/app/bml.png",
"transaction": "FP20260101120000XXXX",
"type": "topup",
"amount": 0.01,
"success": 1,
"status": "Success"
},
{
"date": "2026-03-01 10:00:00",
"name": "Fitr Zakat Payment",
"details": "Payment for Fitr Zakat - 1447",
"icon": "https://fahipay.mv/images/app/zakat_service.png",
"transaction": "FP20260301100000XXXX",
"type": "payment",
"subtype": "FTZKT",
"data": {
"nid": "A123456",
"name": "Mohamed Ali",
"categories": [
{
"type": 7,
"count": 1,
"price": "10.00",
"name": "Normal Wheet"
}
],
"sadaqat": "0.00"
},
"amount": -10,
"success": 1,
"status": "Success"
},
{
"date": "2026-02-01 09:00:00",
"name": "Ooredoo Raastas",
"details": "Mobile Recharge - 9600000001",
"icon": "https://fahipay.mv/images/app/ooredoo.png",
"transaction": "FP20260201090000XXXX",
"type": "payment",
"subtype": "OORCH",
"amount": -100,
"success": 1,
"status": "Success"
}
],
"total": 42,
"next": "https://fahipay.mv/actions/activity/?s=15&l=15",
"type": "success"
}
```
---
## Response Fields
### Top-level
| Field | Type | Description |
|---|---|---|
| `entries` | `array` | List of transaction entries for this page |
| `total` | `number` | Total number of transactions across all pages |
| `next` | `string` | URL of the next page (`null` or absent on last page) |
| `type` | `string` | `"success"` |
---
### Entry Object
| Field | Type | Description |
|---|---|---|
| `date` | `string` | Transaction date/time — format: `YYYY-MM-DD HH:mm:ss` |
| `name` | `string` | Human-readable transaction name (e.g. `"Cash Deposit"`, `"Ooredoo Raastas"`) |
| `details` | `string` | Secondary description (e.g. `"Transferred Via BML ebanking"`, `"Mobile Recharge - 9198026"`) |
| `icon` | `string` | URL of the merchant/bank icon image |
| `transaction` | `string` | Unique transaction reference ID (e.g. `FP20260516151002ZXGD`) |
| `type` | `string` | Transaction category — see table below |
| `subtype` | `string` | Optional service-specific subtype code (e.g. `OORCH`, `DHBPY`) |
| `amount` | `number` | Transaction amount in MVR — **negative = debit, positive = credit** |
| `success` | `number` | `1` = successful, `0` = failed |
| `status` | `string` | Human-readable status string (e.g. `"Success"`, `"Failed"`) |
| `data` | `object` | Optional. Present on some payment types with extra metadata |
---
### Transaction Types (`type` field)
| Value | Description |
|---|---|
| `topup` | Money deposited into the wallet (credit, positive amount) |
| `payment` | Money paid out for a service (debit, negative amount) |
| `transfer` | Peer-to-peer transfer to/from another Fahipay user |
| `withdraw` | Money withdrawn from the wallet to a bank account |
---
### Known Subtypes (`subtype` field)
| Code | Service |
|---|---|
| `OORCH` | Ooredoo Raastas (mobile top-up) |
| `OOBPY` | Ooredoo BillPay |
| `DHRCH` | Dhiraagu Reload (mobile top-up) |
| `DHBPY` | Dhiraagu BillPay |
| `DHPKG` | Dhiraagu Package (data package) |
| `FTZKT` | Fitr Zakat Payment |
---
### Transaction ID Format
```
FP + YYYYMMDDHHMMSS + XXXX
```
Example: `FP20260101120000XXXX`
- `FP` — Fahipay prefix
- `20260101` — date (2026-01-01)
- `120000` — time (12:00:00)
- `XXXX` — 4-char random suffix
---
## Amount Sign Convention
| Sign | Meaning |
|---|---|
| Positive (`+`) | Credit — money received (top-up, incoming transfer) |
| Negative (`-`) | Debit — money spent (payment, withdrawal, outgoing transfer) |
---
## Date Format
All dates are in local Maldives time (UTC+5), formatted as:
```
YYYY-MM-DD HH:mm:ss
```
Example: `2026-05-16 15:10:25`
---
&nbsp;
---
[← Balance](04-balance.md) &nbsp;&nbsp;&nbsp; **Next →** [Profile Picture](06-profile-picture.md)

View File

@@ -0,0 +1,117 @@
# Profile Picture
Fetch the authenticated user's profile picture. The endpoint redirects to the actual image URL.
---
## Endpoint
```
GET https://fahipay.mv/images/profiles/picture/?t={timestamp}
```
---
## Prerequisites
- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md)
- Valid `__Secure-sess` session cookie
---
## Request
### Headers
| Header | Value |
|---|---|
| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
| `User-Agent` | `okhttp/4.12.0` |
| `Accept-Encoding` | `gzip` |
| `Connection` | `Keep-Alive` |
| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` |
### Query Parameters
| Parameter | Description | Example |
|---|---|---|
| `t` | Cache-busting timestamp string | `Sat May 16 2026 14:57:52 GMT+0500` |
The `t` parameter is a URL-encoded timestamp used to prevent browser caching. The value can be any string — the server ignores it for routing purposes.
---
## curl Example
```bash
curl --request GET \
--url 'https://fahipay.mv/images/profiles/picture/?t=Sat%20Jan%2001%202026%2012:00:00%20GMT+0500' \
--compressed \
--header 'Accept-Encoding: gzip' \
--header 'Connection: Keep-Alive' \
--header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'User-Agent: okhttp/4.12.0' \
--header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
```
---
## Response
### Success
The server responds with `HTTP 302` and a `Location` header pointing to the actual image URL.
```
HTTP/1.1 302 Found
Location: https://fahipay.mv/images/profiles/0000/avatar.jpg?v=0000000000
```
Follow the redirect to download the image. The final response is the raw image bytes (`image/jpeg` or `image/png`).
---
### No Picture Set
If the user has not uploaded a profile picture, the redirect points to a default placeholder image:
```
Location: https://fahipay.mv/images/profiles/default.png
```
---
### Error
If the session is invalid, the server returns `HTTP 401` or redirects to an error page.
---
## Implementation Notes
- HTTP clients that follow redirects automatically (e.g. `OkHttpClient` with `followRedirects(true)`) will return the image bytes directly.
- Use `followRedirects(false)` and read the `Location` header if you need the resolved image URL separately.
- The image URL contains the user's `profileID` in the path — this matches the `profileID` field from the [profile response](03-profile.md).
- The `v=` query parameter in the image URL is a version/cache key. It changes when the user updates their picture.
---
## Suggested Usage
```
timestamp = current time formatted as URL-safe string
GET /images/profiles/picture/?t={timestamp}
→ 302 Location: <image URL>
→ GET <image URL>
→ image bytes
```
Cache the downloaded image by `profileID` and re-fetch when the user explicitly refreshes, rather than on every app launch.
---
&nbsp;
---
[← Transaction History](05-history.md)

129
docs/fahipayapi/README.md Normal file
View File

@@ -0,0 +1,129 @@
# Fahipay API Documentation
Reverse-engineered from traffic captures of the Fahipay Android WebView app (`fahipay.mv`).
---
## Overview
Fahipay is a Maldivian digital wallet service. The API uses a mix of `multipart/form-data` POST requests for authentication and simple authenticated `GET` requests for data retrieval.
Authentication is session-based:
- A `__Secure-sess` cookie is set by the server on first contact and must be sent with every request.
- After login (and optional TOTP verification), the server returns an `authID` token that must be sent as an `authid` header with every subsequent request.
---
## Base URL
```
https://fahipay.mv
```
---
## Authentication Model
| Value | How obtained | How used |
|---|---|---|
| `__Secure-sess` cookie | Set by server on first request | Sent automatically via cookie jar |
| `authID` | Returned by `/api/app/login/` or `/api/app/otp/` | Sent as `authid: <value>` header |
Both must be present on every authenticated request.
---
## Common Request Headers
### Login / OTP endpoints
```
Content-Type: multipart/form-data; boundary=<boundary>
accept: application/json
accept-encoding: gzip, deflate, br
connection: keep-alive
user-agent: Mozilla/5.0 (Linux; Android 14; 22101320I Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36
```
### Authenticated data endpoints
```
Accept-Encoding: gzip
Connection: Keep-Alive
User-Agent: okhttp/4.12.0
authid: <authID>
content-type: multipart/form-data
```
---
## Common Form Fields (Device Info)
All login and OTP requests include a standard set of device fields:
| Field | Example value | Notes |
|---|---|---|
| `device[available]` | `true` | Always `true` |
| `device[platform]` | `Android` | Always `Android` |
| `device[uuid]` | `a1b2c3d4e5f60718` | 16 hex chars, generated once per install, persisted |
| `device[model]` | `22101320I` | Device model string |
| `device[manufacturer]` | `Xiaomi` | Device manufacturer |
| `device[isVirtual]` | `false` | Always `false` |
| `device[serial]` | `unknown` | Always `unknown` |
The `device[uuid]` must be consistent across all requests from the same install. Generate it once and store it permanently.
---
## Login Flow
```
Client Server
| |
| POST /api/app/login/ |
| { email=IDCARD, password, ... } |
|---------------------------------->|
| { two_factor_required: bool } |
|<----------------------------------|
| |
| (if two_factor_required=true) |
| POST /api/app/otp/ |
| { code=TOTP, channel=totp, ... } |
|---------------------------------->|
| { authID: "..." } |
|<----------------------------------|
| |
| (if two_factor_required=false) |
| authID already in login response |
| |
| GET /actions/getprofile/ |
| authid: <authID> |
|---------------------------------->|
| { fullname, profileID, ... } |
|<----------------------------------|
| |
| GET /actions/getbalance/ |
| authid: <authID> |
|---------------------------------->|
| { balance: 1.01 } |
|<----------------------------------|
```
---
## Documents
| # | File | Description |
|---|---|---|
| 1 | [Login](01-login.md) | Authenticate with ID card and password |
| 2 | [OTP / 2FA](02-otp.md) | TOTP verification when 2FA is enabled |
| 3 | [Profile](03-profile.md) | Fetch user profile and linked bank accounts |
| 4 | [Balance](04-balance.md) | Fetch wallet balance |
| 5 | [Transaction History](05-history.md) | Paginated activity/transaction history |
| 6 | [Profile Picture](06-profile-picture.md) | Fetch user profile picture |
---
&nbsp;
---
> **Next →** [Login](01-login.md)

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1142.31 277.3"><defs><style>.cls-1{fill:#15bea7;}.cls-2{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="58.15" y1="208.94" x2="22.66" y2="142.19" gradientUnits="userSpaceOnUse"><stop offset="0.2" stop-color="#15b79e"/><stop offset="0.36" stop-color="#15a08b"/><stop offset="0.79" stop-color="#13655c"/><stop offset="1" stop-color="#134e4a"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M720.28,22.78c35,0,58.9,23.29,58.9,57.39,0,41.86-33,71-81.49,71h-38l-11.56,65.71H606.56L640.7,22.78ZM702.1,113.72c21.75,0,34.69-13.32,34.69-32.16,0-13.88-8.25-21.37-25.32-21.37H675.94l-9.63,53.53Z"/><path class="cls-1" d="M298.85,63.61h40.41L312.31,216.86H271.65l3.72-20.05a67,67,0,0,1-49.55,22.33c-32.67,0-58.74-25.19-58.74-66.45,0-48.12,35.23-91.39,81.64-91.39,20.34,0,37.82,8.31,46.69,22.08Zm-12,67.61c0-21.5-12.88-34.11-31.79-34.11-26.65,0-46.11,24.94-46.11,51.86,0,21.78,12.88,34.36,32.07,34.36C267.91,183.33,286.82,157.84,286.82,131.22Z"/><path class="cls-1" d="M510.16,120.9l-16.9,96H452.58l14.61-83.66c4-23.2-4.29-34.65-22.9-34.65-20.06,0-37,13.46-41.26,38.39l-14,79.92H348.31l37-209.13H426L412.5,84A64.85,64.85,0,0,1,462.32,61.3C494.42,61.3,516.47,84.81,510.16,120.9Z"/><path class="cls-1" d="M527.53,216.86,554.47,63.61h40.66L568.21,216.86ZM559,26.07C559,12.33,570.22,0,584.84,0c11.45,0,20.34,7.46,20.34,19.21,0,13.74-11.75,26.35-26.07,26.35C567.63,45.56,559,37.54,559,26.07Z"/><path class="cls-1" d="M921.83,63.61h40.4L935.29,216.86H894.63l3.71-20.05a67,67,0,0,1-49.55,22.33c-32.67,0-58.74-25.19-58.74-66.45,0-48.12,35.24-91.39,81.65-91.39,20.34,0,37.82,8.31,46.69,22.08Zm-12,67.61c0-21.5-12.89-34.11-31.8-34.11-26.64,0-46.11,24.94-46.11,51.86,0,21.78,12.89,34.36,32.07,34.36C890.89,183.33,909.8,157.84,909.8,131.22Z"/><path class="cls-1" d="M1097.63,63.61h44.68l-97.39,176.15c-14.62,26.37-29.79,37.54-60.15,37.54H960.41L967,241.49h16.9c13.46,0,20.07-3.71,26.65-16l4.29-7.44L982.18,63.61H1024l18.91,105.13Z"/><path class="cls-1" d="M156.34,57.58l8.92-49.85H102.89A78.06,78.06,0,0,0,26.05,72.09L23,89.49h0c16.8-22.76,42.41-31.91,70.71-31.91Z"/><path class="cls-1" d="M142.8,134l8.92-49.84H89.35a78,78,0,0,0-76.84,64.35L9.22,166.1a.19.19,0,0,0,.35.15C26.38,144,52.12,134,80.11,134Z"/><path class="cls-2" d="M9.55,166.25a.19.19,0,0,1-.34-.13c-.07.38-.32,1.73-.54,3L0,216.86H43.25l12.28-65.43c2.3-12.38,12.59-17.31,25.15-17.39h-.59C52.1,134,26.37,144,9.55,166.25Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB