Files
thijooree/docs/mfaisaapi/README.md
T
shihaam 43f3cca2aa
Auto Tag on Version Change / check-version (push) Failing after 13m41s
ooredoo mfaisa
2026-06-27 14:25:46 +05:00

99 lines
4.2 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 |
---
&nbsp;
---
> **Next →** [Encryption & Anti-Replay](01-encryption.md)