Files
thijooree/docs/mfaisaapi/02-login.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

221 lines
6.7 KiB
Markdown

# Login
Authenticate a user with their Ooredoo mobile number and 4-digit M-Faisa mPIN.
The flow is two requests:
1. `fetchSubscriberByMDN` — confirms the number has a registered, fully-KYC'd M-Faisa wallet before asking for the mPIN.
2. `doMobileLogin` — submits the mPIN and (on success) returns the session + pocket details.
All RSA encryption used below is specified in detail in [01-encryption.md](01-encryption.md) — the mobile-key cipher is `RSA/ECB/OAEPWithSHA-256AndMGF1Padding` with the plaintext `"960" + msisdn`; the mPin cipher is `RSA/ECB/OAEPWithSHA-1AndMGF1Padding` with the plaintext `pin + <6-char salt>`.
---
## Step 1: `fetchSubscriberByMDN`
Confirms the number has a usable M-Faisa wallet before prompting for the mPIN.
### Endpoint
```
POST https://superapp.ooredoo.mv/api/mfaisaa-bff/mfino/v1.1/web/fetchSubscriberByMDN
```
### Request
**Content-Type:** `application/json; charset=UTF-8`
```json
{ "mdnId": "<encryptMobile(msisdn), base64>" }
```
### curl Example
```bash
MDN_ENC=$(python tmp/mfaisa_encrypt.py mobile <msisdn>)
curl --request POST \
--url https://superapp.ooredoo.mv/api/mfaisaa-bff/mfino/v1.1/web/fetchSubscriberByMDN \
--compressed \
--header 'Content-Type: application/json; charset=UTF-8' \
--header 'Host: superapp.ooredoo.mv' \
--header 'Connection: Keep-Alive' \
--header 'Accept-Encoding: gzip' \
--data "{\"mdnId\":\"${MDN_ENC//=/\\u003d}\"}"
```
> Note the `${MDN_ENC//=/=}` substitution — the server requires the [Gson html-safe `=` escape](01-encryption.md#html-safe-gson--escape).
### Response
```json
{
"success": true,
"message": "Operation completed successfully.",
"kycStatus": "Full KYC",
"name": "<First Name>",
"firstName": "<First Name>",
"lastName": "<Last Name>",
"language": "English",
"activationPending": false,
"passwordCreated": true,
"subscriberRegistered": true,
"userIdCreated": false
}
```
### Decision matrix
| Condition | Thijooree behaviour |
|---|---|
| `subscriberRegistered = false` | Show: *"User not registered. Please use the Ooredoo SuperApp to register your M-Faisa wallet and complete KYC, then come back to Thijooree."* |
| `kycStatus != "Full KYC"` | Show: *"Your M-Faisa wallet needs Full KYC. Please complete KYC in the Ooredoo SuperApp, then come back to Thijooree."* |
| `passwordCreated = false` | Show: *"Set your M-Faisa mPIN in the Ooredoo SuperApp first, then try again."* |
| `activationPending = true` | Show: *"Your M-Faisa wallet activation is still pending. Complete it in the Ooredoo SuperApp first."* |
| Otherwise | Proceed to `doMobileLogin` |
---
## Step 2: `doMobileLogin`
Submits the encrypted mPIN; the response contains the user's wallet pockets (E-Money MVR, optionally IMT MVR and PayPal USD).
### Endpoint
```
POST https://superapp.ooredoo.mv/api/mfaisaa-bff/mfino/v1.1/web/doMobileLogin
```
### Request
**Content-Type:** `application/x-www-form-urlencoded`
| Field | Value |
|---|---|
| `channel` | `C03` (constant) |
| `formData` | JSON object (see below), html-safe-escaped (`=``=`) |
| `formDataCs` | `null` (literal string) |
`formData` JSON object:
```json
{
"deviceGeoInfo": {
"appType": "CustomerAndroid",
"appversion": "1.0",
"deviceId": "<Settings.Secure.ANDROID_ID>",
"deviceManufacturer": "<Build.MANUFACTURER>",
"imieNumber": "<Settings.Secure.ANDROID_ID>",
"ipaddress": "11.22.33.55",
"latitude": "0.0",
"longitude": "0.0",
"simId": "<Settings.Secure.ANDROID_ID>"
},
"mPin": "<encryptPin(mpin), hex>",
"mobileNumber": "<encryptMobile(msisdn), base64>",
"role": "RETAIL_SUBSCRIBER",
"tenantCode": "ooredoo",
"userName": "<encryptMobile(msisdn), base64>"
}
```
Both `mobileNumber` and `userName` encrypt the same plaintext, but encrypt independently (so their ciphertexts differ — OAEP padding randomises the output).
`ipaddress` is the constant `"11.22.33.55"` in the official app — not the device's real IP.
### Response — success
```json
{
"success": true,
"loginExchangeKey": "<opaque hex token>",
"mobileLoginSessionTimeout": "240",
"kycStatus": "Full KYC",
"suscriberId": "<12-digit subscriber id>",
"pocketDetails": [
{
"name": "<Subscriber Name>",
"eMailId": "<user@example.com>",
"mdnId": "<msisdn>",
"roleId": "<12-digit role id>",
"walletId": "<11-digit wallet id>",
"offerId": "<offer id>",
"pocketSummaryDetailsArrayDTO": [
{
"pocketId": "<pocket id>",
"pocketType": "INTERNAL",
"pocketValueType": "EMONEY",
"nickName": "E-Money",
"balanceAmount": { "amount": 0.0, "currencyCode": "MVR" },
"isDefaultPocket": true,
"isSecondaryPocket": false,
"statusType": "ACTIVE",
"displayName": "E-Money"
},
{ "pocketValueType": "PAYPAL_USD", "...": "..." }
]
}
]
}
```
> The typo `suscriberId` (missing `b`) is the **server's** spelling, not ours. The same value also appears as `pocketDetails[0].roleId`.
### Response — wrong PIN
The server returns a **JSON array** (not object) on failure:
```json
[
{
"success": false,
"message": "validation errors",
"error": [
{
"objectName": "Credentials Criteria",
"attributeName": "mPin",
"attributeValue": "MPIN_NOT_VALID",
"errorMessage": "Invalid mobile number/ Password. Please check and retry. If you have forgotten your PIN please go to FORGOT PIN to reset PIN."
}
]
}
]
```
On the **second-to-last** attempt, the `errorMessage` changes to:
```
Provided login details are not valid, One more wrong attempt will lock your account.
```
Thijooree detects the warning by substring (`"one more"` / `"will lock"`, case-insensitive) and surfaces it as a stronger inline error.
### Distinguishing success from failure
The official app — and Thijooree — distinguish the two purely by the JSON shape:
```kotlin
val trimmed = raw.trimStart()
if (trimmed.startsWith("[")) {
// wrong PIN path
} else {
// success path
}
```
---
## Implementation notes
- **Plaintext is `"960" + msisdn`.** The country code is prepended *inside* `MfaisaCrypto.encryptMobile` rather than at the call site.
- **`Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")` is not enough on its own.** You also need the explicit `OAEPParameterSpec` — see [01-encryption.md](01-encryption.md#reference-implementation-kotlin).
- **The mPIN salt must be exactly 6 alphanumeric chars.** Other lengths/charsets work for OAEP locally but were not seen in the official app and aren't worth deviating from.
- **No User-Agent header**, as noted in the [README](README.md).
---
&nbsp;
---
> **Next →** [Transaction History](03-history.md) | **← Back to** [README](README.md)