100 lines
4.3 KiB
Markdown
100 lines
4.3 KiB
Markdown
# Ooredoo M-Faisa API Documentation
|
|
|
|
Reverse-engineered from traffic captures and live Frida hooks of the official Ooredoo SuperApp (`com.mventus.ooredoomaldives`).
|
|
|
|
[Play Store](https://play.google.com/store/apps/details?id=com.mventus.ooredoomaldives)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
M-Faisa is Ooredoo Maldives' mobile wallet, exposed via a JSON/form-encoded REST API on `superapp.ooredoo.mv`. The wire format is unusual in three ways:
|
|
|
|
1. **Field-level RSA encryption.** The MSISDN (`mdnId`, `mobileNumber`, `userName`, `initiatingMDN`, `identifier`) and the mPIN (`mPin`) are each encrypted with a different RSA public key before being placed in the request body. See [01-encryption.md](01-encryption.md).
|
|
2. **Anti-replay envelope.** Every session-scoped form-encoded POST carries an `rndValue` (RSA-encrypted timestamp) and a `csValue` (Adler32 of `formDataJson + nonceStr`). See [01-encryption.md → `rndValue` / `csValue`](01-encryption.md#anti-replay-envelope-rndvalue--csvalue).
|
|
3. **Cloudflare-fingerprinted header order.** A `User-Agent` header sent explicitly (instead of letting OkHttp add it last) returns HTTP 400.
|
|
|
|
---
|
|
|
|
## Base URL
|
|
|
|
```
|
|
https://superapp.ooredoo.mv
|
|
```
|
|
|
|
All M-Faisa endpoints are mounted at `/api/mfaisaa-bff/mfino/v1.1/web/...`.
|
|
|
|
---
|
|
|
|
## Authentication Model
|
|
|
|
| Value | How obtained | How used |
|
|
|---|---|---|
|
|
| `loginExchangeKey` | Returned by `doMobileLogin` on success | Held in memory only; identifies the session |
|
|
| Session timeout | `mobileLoginSessionTimeout` field, default **240 seconds** | After expiry the user must re-login (no refresh-token flow) |
|
|
|
|
Because there is no refresh, Thijooree re-runs `fetchSubscriberByMDN` + `doMobileLogin` on every cold-start refresh, using the saved msisdn + mPIN from `CredentialStore`. The `SESSION_EXPIRED` error envelope is also caught at runtime and the session is silently re-established before retrying the failed request.
|
|
|
|
---
|
|
|
|
## Common Request Headers
|
|
|
|
```
|
|
Content-Type: application/json; charset=UTF-8 (fetchSubscriberByMDN)
|
|
Content-Type: application/x-www-form-urlencoded (every other endpoint)
|
|
Host: superapp.ooredoo.mv
|
|
Connection: Keep-Alive
|
|
Accept-Encoding: gzip
|
|
User-Agent: okhttp/4.12.0
|
|
```
|
|
|
|
> **Do NOT set User-Agent in code.** Cloudflare fingerprints the header order; an explicit `User-Agent` header is pushed to the front of the request and the request is rejected with HTTP 400. Let OkHttp's `BridgeInterceptor` add the default `okhttp/4.12.0` at the end.
|
|
|
|
---
|
|
|
|
## Login Flow
|
|
|
|
```
|
|
Client Server
|
|
| |
|
|
| POST /fetchSubscriberByMDN |
|
|
| { mdnId: encryptMobile(msisdn) } |
|
|
|---------------------------------------------------->|
|
|
| { success, subscriberRegistered, kycStatus, ... } |
|
|
|<----------------------------------------------------|
|
|
| |
|
|
| (abort if subscriberRegistered=false |
|
|
| or kycStatus != "Full KYC") |
|
|
| |
|
|
| POST /doMobileLogin |
|
|
| channel=C03 |
|
|
| formData={ deviceGeoInfo, mPin: encryptPin(mpin), |
|
|
| mobileNumber: ..., userName: ..., |
|
|
| role:"RETAIL_SUBSCRIBER", |
|
|
| tenantCode:"ooredoo" } |
|
|
| formDataCs=null |
|
|
|---------------------------------------------------->|
|
|
| { success, loginExchangeKey, pocketDetails: [...]} |
|
|
|<----------------------------------------------------|
|
|
```
|
|
|
|
---
|
|
|
|
## Documents
|
|
|
|
| # | File | Description |
|
|
|---|---|---|
|
|
| 1 | [Encryption & Anti-Replay](01-encryption.md) | Mobile / mPin RSA, the `rndValue` + `csValue` envelope, the Gson `=` quirk, key-extraction story |
|
|
| 2 | [Login](02-login.md) | Subscriber lookup + mPIN login |
|
|
| 3 | [Transaction History](03-history.md) | Paginated history per session |
|
|
| 4 | [Transfer Money](04-transfer.md) | Three-step wallet-to-wallet send: recipient lookup → initiate (server SMSes OTP) → confirm |
|
|
| 5 | [QR Merchant Payment](05-qr-pay.md) | Three-step "smart pay" scan-to-merchant: QR lookup → initiate → confirm. **No OTP** (`2FARequired=NONE`) |
|
|
|
|
---
|
|
|
|
|
|
|
|
---
|
|
|
|
> **Next →** [Encryption & Anti-Replay](01-encryption.md)
|