# 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": "" } ``` ### curl Example ```bash MDN_ENC=$(python tmp/mfaisa_encrypt.py mobile ) 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": "", "firstName": "", "lastName": "", "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": "", "deviceManufacturer": "", "imieNumber": "", "ipaddress": "11.22.33.55", "latitude": "0.0", "longitude": "0.0", "simId": "" }, "mPin": "", "mobileNumber": "", "role": "RETAIL_SUBSCRIBER", "tenantCode": "ooredoo", "userName": "" } ``` 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": "", "mobileLoginSessionTimeout": "240", "kycStatus": "Full KYC", "suscriberId": "<12-digit subscriber id>", "pocketDetails": [ { "name": "", "eMailId": "", "mdnId": "", "roleId": "<12-digit role id>", "walletId": "<11-digit wallet id>", "offerId": "", "pocketSummaryDetailsArrayDTO": [ { "pocketId": "", "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). ---   --- > **Next →** [Transaction History](03-history.md) | **← Back to** [README](README.md)