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

6.7 KiB

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 — 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

{ "mdnId": "<encryptMobile(msisdn), base64>" }

curl Example

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.

Response

{
  "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:

{
  "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

{
  "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:

[
  {
    "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:

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.
  • 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.

 


Next → Transaction History | ← Back to README