From 7864655a82c84c2b327507ac73005c8583f83ec2 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Sat, 16 May 2026 21:31:34 +0500 Subject: [PATCH] add support for fahipay login and view history --- .gitignore | 1 + .../java/sh/sar/basedbank/BasedBankApp.kt | 3 + .../java/sh/sar/basedbank/MainActivity.kt | 2 +- .../basedbank/api/fahipay/FahipayLoginFlow.kt | 290 ++++++++++++++++++ .../basedbank/api/fahipay/FahipayModels.kt | 21 ++ .../ui/home/AccountHistoryAdapter.kt | 6 +- .../ui/home/AccountHistoryFragment.kt | 21 +- .../sar/basedbank/ui/home/AccountsAdapter.kt | 6 +- .../sh/sar/basedbank/ui/home/HomeActivity.kt | 98 +++++- .../sar/basedbank/ui/home/SettingsFragment.kt | 34 +- .../sar/basedbank/ui/home/TransferFragment.kt | 14 +- .../ui/login/BankSelectionFragment.kt | 4 + .../basedbank/ui/login/CredentialsFragment.kt | 172 ++++++++++- .../sh/sar/basedbank/util/AccountCache.kt | 51 +++ .../sh/sar/basedbank/util/CredentialStore.kt | 110 +++++++ app/src/main/res/drawable/fahipay_logo.png | Bin 0 -> 163168 bytes .../main/res/drawable/fahipay_logo_long.xml | 66 ++++ .../res/layout/fragment_bank_selection.xml | 44 +++ .../main/res/layout/fragment_credentials.xml | 21 ++ app/src/main/res/navigation/login_nav.xml | 13 + app/src/main/res/values/strings.xml | 7 + docs/fahipayapi/01-login.md | 152 +++++++++ docs/fahipayapi/02-otp.md | 158 ++++++++++ docs/fahipayapi/03-profile.md | 229 ++++++++++++++ docs/fahipayapi/04-balance.md | 109 +++++++ docs/fahipayapi/05-history.md | 250 +++++++++++++++ docs/fahipayapi/06-profile-picture.md | 117 +++++++ docs/fahipayapi/README.md | 129 ++++++++ docs/fahipayapi/fahipay_logo_long.svg | 1 + 29 files changed, 2097 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayLoginFlow.kt create mode 100644 app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayModels.kt create mode 100644 app/src/main/res/drawable/fahipay_logo.png create mode 100644 app/src/main/res/drawable/fahipay_logo_long.xml create mode 100644 docs/fahipayapi/01-login.md create mode 100644 docs/fahipayapi/02-otp.md create mode 100644 docs/fahipayapi/03-profile.md create mode 100644 docs/fahipayapi/04-balance.md create mode 100644 docs/fahipayapi/05-history.md create mode 100644 docs/fahipayapi/06-profile-picture.md create mode 100644 docs/fahipayapi/README.md create mode 100644 docs/fahipayapi/fahipay_logo_long.svg diff --git a/.gitignore b/.gitignore index 22b40fc..534e820 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,6 @@ local.properties docs/mibapi/tmp docs/bmlapi/tmp +docs/fahipayapi/tmp tmp app/key.jks diff --git a/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt b/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt index 7f2cf5c..28bb402 100644 --- a/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt +++ b/app/src/main/java/sh/sar/basedbank/BasedBankApp.kt @@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatDelegate import com.google.android.material.color.DynamicColors import kotlinx.coroutines.sync.Mutex import sh.sar.basedbank.api.bml.BmlSession +import sh.sar.basedbank.api.fahipay.FahipaySession import sh.sar.basedbank.api.mib.MibAccount import sh.sar.basedbank.api.mib.MibLoginFlow import sh.sar.basedbank.api.mib.MibProfile @@ -20,6 +21,8 @@ class BasedBankApp : Application() { var mibProfiles: List = emptyList() var bmlSession: BmlSession? = null var bmlAccounts: List = emptyList() + var fahipaySession: FahipaySession? = null + var fahipayAccounts: List = emptyList() /** Serialises all MIB profile-switch + request sequences to prevent session corruption. */ val mibMutex = Mutex() diff --git a/app/src/main/java/sh/sar/basedbank/MainActivity.kt b/app/src/main/java/sh/sar/basedbank/MainActivity.kt index f0b1327..9c1d59a 100644 --- a/app/src/main/java/sh/sar/basedbank/MainActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/MainActivity.kt @@ -16,7 +16,7 @@ class MainActivity : AppCompatActivity() { val onboardingDone = prefs.getBoolean("onboarding_done", false) val securitySet = prefs.getString("security_method", null) != null val store = CredentialStore(this) - val hasCredentials = store.hasMibCredentials() || store.hasBmlCredentials() + val hasCredentials = store.hasMibCredentials() || store.hasBmlCredentials() || store.hasFahipayCredentials() val target = when { !onboardingDone -> OnboardingActivity::class.java diff --git a/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayLoginFlow.kt b/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayLoginFlow.kt new file mode 100644 index 0000000..d53fec0 --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayLoginFlow.kt @@ -0,0 +1,290 @@ +package sh.sar.basedbank.api.fahipay + +import android.os.Build +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody +import okio.Buffer +import org.json.JSONObject +import sh.sar.basedbank.api.mib.MibAccount +import sh.sar.basedbank.api.mib.Transaction +import java.security.SecureRandom +import java.util.concurrent.TimeUnit + +class FahipayLoginFlow { + + private val BASE_URL = "https://fahipay.mv" + private val UA_WEBVIEW = "Mozilla/5.0 (Linux; Android 14; ${Build.MODEL} Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36" + private val UA_OKHTTP = "okhttp/4.12.0" + private val PAGE_SIZE = 15 + + private val cookieStore = mutableMapOf>() + private val cookieJar = object : CookieJar { + override fun saveFromResponse(url: HttpUrl, cookies: List) { + val list = cookieStore.getOrPut(url.host) { mutableListOf() } + for (c in cookies) { + list.removeAll { it.name == c.name } + list.add(c) + } + } + override fun loadForRequest(url: HttpUrl): List = + cookieStore[url.host] ?: emptyList() + } + + private val client = OkHttpClient.Builder() + .cookieJar(cookieJar) + .followRedirects(false) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + + /** Seed the cookie jar with a stored session cookie before using a persisted session. */ + fun setSessionCookie(value: String) { + val host = "fahipay.mv" + val list = cookieStore.getOrPut(host) { mutableListOf() } + list.removeAll { it.name == "__Secure-sess" } + list.add( + Cookie.Builder() + .domain(host) + .name("__Secure-sess") + .value(value) + .secure() + .build() + ) + } + + fun getSessionCookieValue(): String? = + cookieStore["fahipay.mv"]?.firstOrNull { it.name == "__Secure-sess" }?.value + + // Establishes the __Secure-sess cookie required for the login+OTP flow. + private fun initSession() { + client.newCall( + Request.Builder().url("$BASE_URL/api/app/lang/data/") + .get() + .header("User-Agent", UA_WEBVIEW) + .build() + ).execute().close() + } + + /** + * Step 1: POST /api/app/login/ + * Returns FahipayLoginStep: + * twoFactorRequired=false + authId set → login complete, proceed + * twoFactorRequired=true + authId=null → call verifyTotp() next + */ + fun login(idCard: String, password: String, deviceUuid: String): FahipayLoginStep { + initSession() + val body = buildFormBody( + "email" to idCard, + "password" to password, + "grant_type" to "auth_id", + "lang" to "en", + "version" to "2.0.0", + "platform" to "app", + *deviceParts(deviceUuid) + ) + + val resp = client.newCall( + Request.Builder().url("$BASE_URL/api/app/login/") + .post(body) + .header("User-Agent", UA_WEBVIEW) + .header("accept", "application/json") + .build() + ).execute() + val json = resp.body?.string() ?: throw Exception("Empty login response") + resp.close() + + val obj = JSONObject(json) + if (obj.optString("type") != "success") { + throw Exception(obj.optString("msg", "Login failed — check your ID card and password")) + } + + val authId = obj.optString("authID", "").takeIf { it.isNotBlank() } + val twoFa = obj.optBoolean("two_factor_required", false) + return FahipayLoginStep(twoFactorRequired = twoFa, authId = authId) + } + + /** + * Step 2 (if 2FA required): POST /api/app/otp/ + * Returns authId. + */ + fun verifyTotp(code: String, deviceUuid: String): String { + val body = buildFormBody( + "code" to code, + "channel" to "totp", + "action" to "login", + "grant_type" to "auth_id", + "lang" to "en", + "version" to "2.0.0", + "platform" to "app", + *deviceParts(deviceUuid) + ) + + val resp = client.newCall( + Request.Builder().url("$BASE_URL/api/app/otp/") + .post(body) + .header("User-Agent", UA_WEBVIEW) + .header("accept", "application/json") + .build() + ).execute() + val json = resp.body?.string() ?: throw Exception("Empty OTP response") + resp.close() + + val obj = JSONObject(json) + if (obj.optString("type") != "success") { + throw Exception(obj.optString("msg", "OTP verification failed")) + } + return obj.optString("authID").takeIf { it.isNotBlank() } + ?: throw Exception("No authID in OTP response") + } + + fun fetchProfile(session: FahipaySession): FahipayUserProfile { + val resp = client.newCall( + Request.Builder().url("$BASE_URL/actions/getprofile/?lang=en") + .header("authid", session.authId) + .header("content-type", "multipart/form-data") + .header("User-Agent", UA_OKHTTP) + .build() + ).execute() + val json = resp.body?.string() ?: throw Exception("Empty profile response") + resp.close() + + val obj = JSONObject(json) + val props = obj.optJSONObject("props") ?: JSONObject() + return FahipayUserProfile( + fullName = obj.optString("fullname").trim(), + email = obj.optString("email").trim(), + mobile = obj.optString("mobile").trim(), + nid = obj.optString("nid").trim(), + profileId = obj.optString("profileID").trim(), + walletAccount = props.optString("acc", ""), + linkedAccounts = props.optJSONObject("accs")?.toString() ?: "{}" + ) + } + + fun fetchBalance(session: FahipaySession): Double { + val resp = client.newCall( + Request.Builder().url("$BASE_URL/actions/getbalance/?lang=en") + .header("authid", session.authId) + .header("content-type", "multipart/form-data") + .header("User-Agent", UA_OKHTTP) + .build() + ).execute() + val json = resp.body?.string() ?: return 0.0 + resp.close() + return try { + val obj = JSONObject(json) + if (obj.optBoolean("error")) 0.0 else obj.optDouble("balance", 0.0) + } catch (_: Exception) { 0.0 } + } + + fun buildAccount(profile: FahipayUserProfile, balance: Double, loginTag: String): MibAccount = + MibAccount( + profileName = profile.fullName.ifBlank { "Fahipay" }, + profileType = "FAHIPAY", + accountNumber = profile.walletAccount, + accountBriefName = "Fahipay Wallet", + currencyName = "MVR", + accountTypeName = "Digital Wallet", + availableBalance = "%.2f".format(balance), + currentBalance = "%.2f".format(balance), + blockedAmount = "0.00", + mvrBalance = "%.2f".format(balance), + statusDesc = "Active", + profileImageHash = null, + loginTag = loginTag, + internalId = profile.profileId + ) + + /** + * Fetches paginated activity history. + * @param start offset (0-based) + * @return Pair of (transactions, total count) + */ + fun fetchHistory( + session: FahipaySession, + accountDisplayName: String, + accountNumber: String, + start: Int + ): Pair, Int> { + val resp = client.newCall( + Request.Builder() + .url("$BASE_URL/actions/activity/?s=$start&l=$PAGE_SIZE&lang=en") + .header("authid", session.authId) + .header("content-type", "multipart/form-data") + .header("User-Agent", UA_OKHTTP) + .build() + ).execute() + val json = resp.body?.string() ?: return Pair(emptyList(), 0) + resp.close() + return try { + val obj = JSONObject(json) + val total = obj.optInt("total", 0) + val entries = obj.optJSONArray("entries") ?: return Pair(emptyList(), total) + val list = (0 until entries.length()).map { i -> + val e = entries.getJSONObject(i) + Transaction( + id = e.optString("transaction"), + date = e.optString("date"), + description = e.optString("name").trim(), + amount = e.optDouble("amount", 0.0), + currency = "MVR", + counterpartyName = e.optString("details").takeIf { it.isNotBlank() }, + reference = e.optString("transaction").takeIf { it.isNotBlank() }, + accountNumber = accountNumber, + accountDisplayName = accountDisplayName, + source = "FAHIPAY" + ) + } + Pair(list, total) + } catch (_: Exception) { Pair(emptyList(), 0) } + } + + private fun deviceParts(deviceUuid: String): Array> = arrayOf( + "device[available]" to "true", + "device[platform]" to "Android", + "device[uuid]" to deviceUuid, + "device[model]" to Build.MODEL, + "device[manufacturer]" to Build.MANUFACTURER, + "device[isVirtual]" to "false", + "device[serial]" to "unknown" + ) + + /** + * Builds a multipart/form-data body with lowercase "content-disposition" headers, + * which is what the Fahipay server requires. + */ + private fun buildFormBody(vararg parts: Pair): RequestBody { + val boundary = java.util.UUID.randomUUID().toString() + val buf = Buffer() + for ((name, value) in parts) { + val valueBytes = value.toByteArray(Charsets.UTF_8) + buf.writeUtf8("--$boundary\r\n") + buf.writeUtf8("content-disposition: form-data; name=\"$name\"\r\n") + buf.writeUtf8("Content-Length: ${valueBytes.size}\r\n") + buf.writeUtf8("\r\n") + buf.write(valueBytes) + buf.writeUtf8("\r\n") + } + buf.writeUtf8("--$boundary--\r\n") + val snapshot = buf.readByteString() + val mediaType = "multipart/form-data; boundary=$boundary".toMediaType() + return object : RequestBody() { + override fun contentType() = mediaType + override fun contentLength() = snapshot.size.toLong() + override fun writeTo(sink: okio.BufferedSink) { sink.write(snapshot) } + } + } + + companion object { + fun generateDeviceUuid(): String { + val bytes = ByteArray(8) + SecureRandom().nextBytes(bytes) + return bytes.joinToString("") { "%02x".format(it) } + } + } +} diff --git a/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayModels.kt b/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayModels.kt new file mode 100644 index 0000000..1471d83 --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/api/fahipay/FahipayModels.kt @@ -0,0 +1,21 @@ +package sh.sar.basedbank.api.fahipay + +data class FahipaySession( + val authId: String, + val sessionCookie: String +) + +data class FahipayUserProfile( + val fullName: String, + val email: String, + val mobile: String, + val nid: String, + val profileId: String, + val walletAccount: String, + val linkedAccounts: String // raw JSON of props.accs, for transfer use +) + +data class FahipayLoginStep( + val twoFactorRequired: Boolean, + val authId: String? = null // non-null when 2FA not required +) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryAdapter.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryAdapter.kt index 08660af..74db563 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryAdapter.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryAdapter.kt @@ -139,7 +139,11 @@ class AccountHistoryAdapter( fun bind(acc: MibAccount) { b.tvHeaderAccountName.text = acc.accountBriefName b.tvHeaderAccountNumber.text = acc.accountNumber - b.tvHeaderPillBank.text = if (acc.profileType.startsWith("BML")) "BML" else "MIB" + b.tvHeaderPillBank.text = when { + acc.profileType.startsWith("BML") -> "BML" + acc.profileType == "FAHIPAY" -> "FP" + else -> null + } b.tvHeaderPillType.text = friendlyType(acc.accountTypeName) b.tvHeaderAvailable.text = "${acc.currencyName} ${acc.availableBalance}" b.tvHeaderBalance.text = "${acc.currencyName} ${acc.currentBalance}" diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt index 3108fe5..80324b4 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.api.bml.BmlLoginFlow +import sh.sar.basedbank.api.fahipay.FahipayLoginFlow import sh.sar.basedbank.api.mib.MibAccount import sh.sar.basedbank.api.mib.MibContactsClient import sh.sar.basedbank.api.mib.MibHistoryClient @@ -50,6 +51,8 @@ class AccountHistoryFragment : Fragment() { private var bmlNextPage = 1 private var bmlTotalPages = -1 private var cardMonthOffset = 0 // 0 = current month, 1 = prev, etc. + private var fahipayNextStart = 0 + private var fahipayTotal = -1 private var isLoading = false private val pageSize = 10 @@ -121,10 +124,12 @@ class AccountHistoryFragment : Fragment() { binding.emptyView.visibility = if (filtered.isEmpty() && !isLoading) View.VISIBLE else View.GONE } - private fun isMib() = !account.profileType.startsWith("BML") + private fun isMib() = !account.profileType.startsWith("BML") && account.profileType != "FAHIPAY" private fun isBmlCard() = account.profileType == "BML_PREPAID" + private fun isFahipay() = account.profileType == "FAHIPAY" private fun hasMore(): Boolean = when { + isFahipay() -> fahipayTotal < 0 || fahipayNextStart < fahipayTotal isMib() -> mibTotalCount < 0 || mibNextStart <= mibTotalCount isBmlCard() -> cardMonthOffset < 3 // load up to 3 months else -> bmlTotalPages < 0 || bmlNextPage <= bmlTotalPages @@ -143,6 +148,20 @@ class AccountHistoryFragment : Fragment() { lifecycleScope.launch { val transactions: List = withContext(Dispatchers.IO) { when { + isFahipay() -> { + val session = app.fahipaySession ?: return@withContext emptyList() + val flow = FahipayLoginFlow() + flow.setSessionCookie(session.sessionCookie) + val (list, total) = flow.fetchHistory( + session = session, + accountDisplayName = account.accountBriefName, + accountNumber = account.accountNumber, + start = fahipayNextStart + ) + if (total > 0) fahipayTotal = total + fahipayNextStart += list.size + list + } isMib() -> { val session = app.mibSession ?: return@withContext emptyList() app.mibMutex.withLock { diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt index 5125266..1eaa108 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt @@ -91,7 +91,11 @@ class AccountsAdapter( fun bind(account: MibAccount) { binding.tvAccountName.text = account.accountBriefName binding.tvAccountNumber.text = account.accountNumber - binding.tvPillBank.text = if (account.profileType.startsWith("BML")) "BML" else "MIB" + binding.tvPillBank.text = when { + account.profileType.startsWith("BML") -> "BML" + account.profileType == "FAHIPAY" -> "FP" + else -> null + } binding.tvPillType.text = friendlyAccountType(account.accountTypeName) binding.tvPillProfile.text = when (account.profileType) { "0" -> "Personal" diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index 7e99a0d..038020e 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -31,6 +31,8 @@ import sh.sar.basedbank.R import sh.sar.basedbank.api.bml.AuthExpiredException import sh.sar.basedbank.api.bml.BmlLoginFlow import sh.sar.basedbank.api.bml.BmlSession +import sh.sar.basedbank.api.fahipay.FahipayLoginFlow +import sh.sar.basedbank.api.fahipay.FahipaySession import sh.sar.basedbank.api.mib.MibAccount import sh.sar.basedbank.api.mib.MibLoginFlow import sh.sar.basedbank.databinding.ActivityHomeBinding @@ -104,13 +106,14 @@ class HomeActivity : AppCompatActivity() { // Load data val app = application as BasedBankApp - if (app.accounts.isNotEmpty() || app.bmlAccounts.isNotEmpty()) { + if (app.accounts.isNotEmpty() || app.bmlAccounts.isNotEmpty() || app.fahipayAccounts.isNotEmpty()) { // Came from fresh manual login — accounts ready, rest fetched in background - val mibAccounts = app.accounts.filter { it.profileType != "BML" } - val merged = mibAccounts + app.bmlAccounts + val mibAccounts = app.accounts.filter { !it.profileType.startsWith("BML") && it.profileType != "FAHIPAY" } + val merged = mibAccounts + app.bmlAccounts + app.fahipayAccounts viewModel.accounts.value = merged if (mibAccounts.isNotEmpty()) AccountCache.save(this, mibAccounts) if (app.bmlAccounts.isNotEmpty()) AccountCache.saveBml(this, app.bmlAccounts) + if (app.fahipayAccounts.isNotEmpty()) AccountCache.saveFahipay(this, app.fahipayAccounts) val cachedFinancing = FinancingCache.load(this) if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing @@ -131,7 +134,8 @@ class HomeActivity : AppCompatActivity() { // Came from lock screen — show caches immediately, refresh everything in background val cachedMib = AccountCache.load(this) val cachedBml = AccountCache.loadBml(this) - val merged = cachedMib + cachedBml + val cachedFahipay = AccountCache.loadFahipay(this) + val merged = cachedMib + cachedBml + cachedFahipay if (merged.isNotEmpty()) viewModel.accounts.value = merged val cachedFinancing = FinancingCache.load(this) if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing @@ -143,7 +147,7 @@ class HomeActivity : AppCompatActivity() { if (cachedLimits.isNotEmpty()) viewModel.bmlLimits.value = cachedLimits val store = CredentialStore(this) - autoRefresh(store.loadMibCredentials(), store.loadBmlCredentials(), store) + autoRefresh(store.loadMibCredentials(), store.loadBmlCredentials(), store.loadFahipayCredentials(), store) } // Show dashboard on first create @@ -289,7 +293,8 @@ class HomeActivity : AppCompatActivity() { val store = CredentialStore(this) val hasMib = store.hasMibCredentials() val hasBml = store.hasBmlCredentials() - if (!hasMib && !hasBml) { + val hasFahipay = store.hasFahipayCredentials() + if (!hasMib && !hasBml && !hasFahipay) { startActivity(Intent(this, LoginActivity::class.java)) finish() return @@ -297,19 +302,21 @@ class HomeActivity : AppCompatActivity() { // Immediately drop accounts for logged-out banks from the displayed list val current = viewModel.accounts.value ?: emptyList() viewModel.accounts.value = current.filter { acc -> - if (!hasMib && !acc.profileType.startsWith("BML")) return@filter false + if (!hasMib && !acc.profileType.startsWith("BML") && acc.profileType != "FAHIPAY") return@filter false if (!hasBml && acc.profileType.startsWith("BML")) return@filter false + if (!hasFahipay && acc.profileType == "FAHIPAY") return@filter false true } - autoRefresh(store.loadMibCredentials(), store.loadBmlCredentials(), store) + autoRefresh(store.loadMibCredentials(), store.loadBmlCredentials(), store.loadFahipayCredentials(), store) } private fun autoRefresh( mibCreds: CredentialStore.MibCredentials?, bmlCreds: CredentialStore.BmlCredentials?, + fahipayCreds: CredentialStore.FahipayCredentials?, store: CredentialStore ) { - if (mibCreds == null && bmlCreds == null) return + if (mibCreds == null && bmlCreds == null && fahipayCreds == null) return binding.refreshIndicator.visibility = View.VISIBLE lifecycleScope.launch { @@ -366,10 +373,62 @@ class HomeActivity : AppCompatActivity() { } } + val fahipayJob = fahipayCreds?.let { creds -> + async(Dispatchers.IO) { + val fahipayFlow = FahipayLoginFlow() + val deviceUuid = store.getOrCreateFahipayDeviceUuid() + + // Try cached session first + val savedSession = store.loadFahipaySession() + if (savedSession != null) { + try { + val session = FahipaySession(savedSession.first, savedSession.second) + fahipayFlow.setSessionCookie(session.sessionCookie) + val balance = fahipayFlow.fetchBalance(session) + val profile = fahipayFlow.fetchProfile(session) + val loginTag = "fahipay_${profile.profileId}" + val accounts = listOf(fahipayFlow.buildAccount(profile, balance, loginTag)) + val app = application as BasedBankApp + app.fahipaySession = session + app.fahipayAccounts = accounts + AccountCache.saveFahipay(this@HomeActivity, accounts) + return@async Pair(session, accounts) + } catch (_: Exception) { + // Session expired — fall through to full login + } + } + + // Full re-login (only works if user has no 2FA, or 2FA was skipped) + try { + val step = fahipayFlow.login(creds.idCard, creds.password, deviceUuid) + if (step.twoFactorRequired) { + // Can't auto-complete 2FA — use cached data + return@async Pair(null, AccountCache.loadFahipay(this@HomeActivity)) + } + val authId = step.authId ?: return@async Pair(null, AccountCache.loadFahipay(this@HomeActivity)) + val cookieValue = fahipayFlow.getSessionCookieValue() ?: "" + val session = FahipaySession(authId, cookieValue) + store.saveFahipaySession(authId, cookieValue) + val profile = fahipayFlow.fetchProfile(session) + val balance = fahipayFlow.fetchBalance(session) + val loginTag = "fahipay_${profile.profileId}" + val accounts = listOf(fahipayFlow.buildAccount(profile, balance, loginTag)) + val app = application as BasedBankApp + app.fahipaySession = session + app.fahipayAccounts = accounts + AccountCache.saveFahipay(this@HomeActivity, accounts) + Pair(session, accounts) + } catch (_: Exception) { + Pair(null, AccountCache.loadFahipay(this@HomeActivity)) + } + } + } + val mibAccounts = mibJob?.await() ?: AccountCache.load(this@HomeActivity) val (bmlSession, bmlAccounts) = bmlJob?.await() ?: Pair(null, AccountCache.loadBml(this@HomeActivity)) + val (_, fahipayAccounts) = fahipayJob?.await() ?: Pair(null, AccountCache.loadFahipay(this@HomeActivity)) - viewModel.accounts.postValue(mibAccounts + bmlAccounts) + viewModel.accounts.postValue(mibAccounts + bmlAccounts + fahipayAccounts) binding.refreshIndicator.visibility = View.GONE val app = application as BasedBankApp @@ -464,7 +523,24 @@ class HomeActivity : AppCompatActivity() { val app = application as BasedBankApp lifecycleScope.launch { val current = viewModel.accounts.value ?: emptyList() - if (src.profileType.startsWith("BML")) { + if (src.profileType == "FAHIPAY") { + val fresh = withContext(Dispatchers.IO) { + val sess = app.fahipaySession ?: return@withContext null + try { + val flow = FahipayLoginFlow() + flow.setSessionCookie(sess.sessionCookie) + val balance = flow.fetchBalance(sess) + val profile = flow.fetchProfile(sess) + val loginTag = "fahipay_${profile.profileId}" + val accounts = listOf(flow.buildAccount(profile, balance, loginTag)) + AccountCache.saveFahipay(this@HomeActivity, accounts) + app.fahipayAccounts = accounts + accounts + } catch (_: Exception) { null } + } ?: return@launch + val others = current.filter { it.profileType != "FAHIPAY" } + viewModel.accounts.postValue(others + fresh) + } else if (src.profileType.startsWith("BML")) { val fresh = withContext(Dispatchers.IO) { val sess = app.bmlSession ?: return@withContext null try { diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt index ab8443e..b7104d3 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt @@ -163,15 +163,16 @@ class SettingsFragment : Fragment() { val hasMib = store.hasMibCredentials() val hasBml = store.hasBmlCredentials() + val hasFahipay = store.hasFahipayCredentials() - binding.tvLoginsTitle.visibility = if (hasMib || hasBml) View.VISIBLE else View.GONE + binding.tvLoginsTitle.visibility = if (hasMib || hasBml || hasFahipay) View.VISIBLE else View.GONE if (hasMib) { val profile = store.loadMibUserProfile() val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.mib_name) val profileNames = AccountCache.load(ctx) .map { it.profileName }.filter { it.isNotBlank() }.distinct() - addLoginRow(container, R.drawable.mib_faisanet_logo, displayName) { + addLoginRow(container, R.drawable.mib_logo, displayName) { showLoginDetails( title = getString(R.string.mib_name), details = buildString { @@ -213,6 +214,23 @@ class SettingsFragment : Fragment() { ) } } + + if (hasFahipay) { + val profile = store.loadFahipayUserProfile() + val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.fahipay_name) + addLoginRow(container, R.drawable.fahipay_logo, displayName) { + showLoginDetails( + title = getString(R.string.fahipay_name), + details = buildString { + if (!profile?.fullName.isNullOrBlank()) appendLine("${getString(R.string.login_detail_name)}: ${profile!!.fullName}") + if (!profile?.email.isNullOrBlank()) appendLine("${getString(R.string.login_detail_email)}: ${profile!!.email}") + if (!profile?.mobile.isNullOrBlank()) appendLine("${getString(R.string.login_detail_mobile)}: ${profile!!.mobile}") + if (!profile?.nid.isNullOrBlank()) appendLine("${getString(R.string.login_detail_id_card)}: ${profile!!.nid}") + }.trim(), + onLogout = { confirmLogout(getString(R.string.fahipay_name)) { logoutFahipay(store) } } + ) + } + } } private fun addLoginRow( @@ -298,6 +316,18 @@ class SettingsFragment : Fragment() { buildLoginsSection() } + private fun logoutFahipay(store: CredentialStore) { + val ctx = requireContext() + store.clearFahipayCredentials() + store.clearFahipaySession() + val app = requireActivity().application as BasedBankApp + app.fahipaySession = null + app.fahipayAccounts = emptyList() + clearAllCaches(ctx) + (activity as HomeActivity).relogin() + buildLoginsSection() + } + private fun applyFlagSecure(enabled: Boolean) { val win = activity?.window ?: return if (enabled) { diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt index c337916..94b0431 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt @@ -167,8 +167,16 @@ class TransferFragment : Fragment() { private fun showFromCard(account: MibAccount) { val isBml = account.profileType.startsWith("BML") - val colorHex = if (isBml) "#0066A1" else "#FE860E" - val bankLabel = if (isBml) "BML" else "MIB" + val colorHex = when { + isBml -> "#0066A1" + account.profileType == "FAHIPAY" -> "#15BEA7" + else -> "#FE860E" + } + val bankLabel = when { + isBml -> "BML" + account.profileType == "FAHIPAY" -> "FP" + else -> null + } val typeLabel = when { account.profileType == "BML_PREPAID" -> "Prepaid Card" account.accountTypeName.isNotBlank() -> account.accountTypeName @@ -177,7 +185,7 @@ class TransferFragment : Fragment() { binding.tvFromAccountName.text = account.accountBriefName binding.tvFromAccountNumber.text = account.accountNumber - binding.tvFromAccountDetails.text = "$bankLabel · $typeLabel · ${account.currencyName} ${account.availableBalance}" + binding.tvFromAccountDetails.text = listOfNotNull(bankLabel, typeLabel, "${account.currencyName} ${account.availableBalance}").joinToString(" · ") binding.ivFromPhoto.setImageBitmap(makeInitialsBitmap(account.accountBriefName, colorHex)) binding.tilFrom.visibility = View.GONE binding.cardFromInfo.visibility = View.VISIBLE diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/BankSelectionFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/login/BankSelectionFragment.kt index eb910c1..c3ea386 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/BankSelectionFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/BankSelectionFragment.kt @@ -27,6 +27,10 @@ class BankSelectionFragment : Fragment() { val args = android.os.Bundle().apply { putString("bankType", "BML") } findNavController().navigate(R.id.action_bankSelection_to_credentials_bml, args) } + binding.cardFahipay.setOnClickListener { + val args = android.os.Bundle().apply { putString("bankType", "FAHIPAY") } + findNavController().navigate(R.id.action_bankSelection_to_credentials_fahipay, args) + } } override fun onDestroyView() { diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt index 060a3f3..37ffa53 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt @@ -19,6 +19,8 @@ import sh.sar.basedbank.util.Totp import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.R import sh.sar.basedbank.api.bml.BmlLoginFlow +import sh.sar.basedbank.api.fahipay.FahipayLoginFlow +import sh.sar.basedbank.api.fahipay.FahipaySession import sh.sar.basedbank.api.mib.MibLoginFlow import sh.sar.basedbank.util.AccountCache import sh.sar.basedbank.util.CredentialStore @@ -40,29 +42,45 @@ class CredentialsFragment : Fragment() { private var _binding: FragmentCredentialsBinding? = null private val binding get() = _binding!! + // Fahipay two-step state + private var fahipayFlow: FahipayLoginFlow? = null + private var fahipayAwaitingTotp = false + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentCredentialsBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - if (bankType == "BML") { - binding.ivBankLogo.setImageResource(R.drawable.bml_logo_vector) - binding.tvSignInDesc.setText(R.string.bml_sign_in_desc) + when (bankType) { + "BML" -> { + binding.ivBankLogo.setImageResource(R.drawable.bml_logo_vector) + binding.tvSignInDesc.setText(R.string.bml_sign_in_desc) + } + "FAHIPAY" -> { + binding.ivBankLogo.setImageResource(R.drawable.fahipay_logo_long) + binding.tvSignInDesc.setText(R.string.fahipay_sign_in_desc) + binding.tilUsername.hint = getString(R.string.fahipay_id_card) + binding.tilOtpSeed.visibility = android.view.View.GONE + binding.etOtpSeed.isEnabled = false + binding.etOtpSeed.isFocusable = false + } } binding.btnLogin.setOnClickListener { attemptLogin() } - binding.etOtpSeed.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { updateOtpDisplay() } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - }) + if (bankType != "FAHIPAY") { + binding.etOtpSeed.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) { updateOtpDisplay() } + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + }) + } } override fun onResume() { super.onResume() - otpHandler.post(otpRunnable) + if (bankType != "FAHIPAY") otpHandler.post(otpRunnable) } override fun onPause() { @@ -91,9 +109,9 @@ class CredentialsFragment : Fragment() { } private fun attemptLogin() { - if (bankType == "BML") { - attemptBmlLogin() - return + when (bankType) { + "BML" -> { attemptBmlLogin(); return } + "FAHIPAY" -> { attemptFahipayLogin(); return } } val username = binding.etUsername.text.toString().trim() @@ -208,6 +226,136 @@ class CredentialsFragment : Fragment() { } } + private fun attemptFahipayLogin() { + if (fahipayAwaitingTotp) { + submitFahipayTotp() + return + } + + val idCard = binding.etUsername.text.toString().trim() + val password = binding.etPassword.text.toString() + + if (idCard.isEmpty() || password.isEmpty()) { + binding.tvError.text = "Please fill in all fields" + binding.tvError.visibility = View.VISIBLE + return + } + + binding.tvError.visibility = View.GONE + binding.progressBar.visibility = View.VISIBLE + binding.btnLogin.isEnabled = false + + val store = CredentialStore(requireContext()) + val deviceUuid = store.getOrCreateFahipayDeviceUuid() + val flow = FahipayLoginFlow().also { fahipayFlow = it } + + viewLifecycleOwner.lifecycleScope.launch { + try { + val step = withContext(Dispatchers.IO) { + flow.login(idCard, password, deviceUuid) + } + + if (step.twoFactorRequired) { + // Show TOTP input, disable ID + password fields + fahipayAwaitingTotp = true + binding.etUsername.isEnabled = false + binding.etPassword.isEnabled = false + binding.tilTotpCode.visibility = View.VISIBLE + binding.btnLogin.text = getString(R.string.fahipay_verify) + binding.tvError.visibility = View.GONE + } else { + // No 2FA — finish login with the authId from the login response + val authId = step.authId ?: throw Exception("No authID received") + val cookieValue = flow.getSessionCookieValue() ?: "" + finishFahipayLogin(flow, FahipaySession(authId, cookieValue), idCard, password, store) + } + } catch (e: Exception) { + binding.tvError.text = e.message ?: "Login failed" + binding.tvError.visibility = View.VISIBLE + } finally { + binding.progressBar.visibility = View.GONE + binding.btnLogin.isEnabled = true + } + } + } + + private fun submitFahipayTotp() { + val code = binding.etTotpCode.text.toString().trim() + if (code.length != 6) { + binding.tvError.text = "Enter the 6-digit code from your authenticator app" + binding.tvError.visibility = View.VISIBLE + return + } + + binding.tvError.visibility = View.GONE + binding.progressBar.visibility = View.VISIBLE + binding.btnLogin.isEnabled = false + + val flow = fahipayFlow ?: run { + binding.tvError.text = "Session lost — please restart login" + binding.tvError.visibility = View.VISIBLE + binding.progressBar.visibility = View.GONE + binding.btnLogin.isEnabled = true + return + } + + val store = CredentialStore(requireContext()) + val deviceUuid = store.getOrCreateFahipayDeviceUuid() + val idCard = binding.etUsername.text.toString().trim() + val password = binding.etPassword.text.toString() + + viewLifecycleOwner.lifecycleScope.launch { + try { + val authId = withContext(Dispatchers.IO) { flow.verifyTotp(code, deviceUuid) } + val cookieValue = flow.getSessionCookieValue() ?: "" + finishFahipayLogin(flow, FahipaySession(authId, cookieValue), idCard, password, store) + } catch (e: Exception) { + binding.tvError.text = e.message ?: "OTP verification failed" + binding.tvError.visibility = View.VISIBLE + } finally { + binding.progressBar.visibility = View.GONE + binding.btnLogin.isEnabled = true + } + } + } + + private suspend fun finishFahipayLogin( + flow: FahipayLoginFlow, + session: FahipaySession, + idCard: String, + password: String, + store: CredentialStore + ) { + val (profile, balance) = withContext(Dispatchers.IO) { + val p = flow.fetchProfile(session) + val b = flow.fetchBalance(session) + Pair(p, b) + } + val loginTag = "fahipay_${profile.profileId}" + val account = flow.buildAccount(profile, balance, loginTag) + store.saveFahipayCredentials(idCard, password) + store.saveFahipaySession(session.authId, session.sessionCookie) + store.saveFahipayUserProfile( + CredentialStore.FahipayUserProfile( + fullName = profile.fullName, + email = profile.email, + mobile = profile.mobile, + nid = profile.nid, + profileId = profile.profileId, + walletAccount = profile.walletAccount, + linkedAccounts = profile.linkedAccounts + ) + ) + AccountCache.saveFahipay(requireContext(), listOf(account)) + val app = requireActivity().application as BasedBankApp + app.fahipaySession = session + app.fahipayAccounts = listOf(account) + app.accounts = app.accounts + listOf(account) + val intent = Intent(requireContext(), HomeActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK + startActivity(intent) + } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/java/sh/sar/basedbank/util/AccountCache.kt b/app/src/main/java/sh/sar/basedbank/util/AccountCache.kt index 42350a2..222153c 100644 --- a/app/src/main/java/sh/sar/basedbank/util/AccountCache.kt +++ b/app/src/main/java/sh/sar/basedbank/util/AccountCache.kt @@ -10,6 +10,7 @@ object AccountCache { private const val PREFS = "account_cache" private const val KEY_MIB = "mib_accounts" private const val KEY_BML = "bml_accounts" + private const val KEY_FAHIPAY = "fahipay_accounts" fun save(context: Context, accounts: List) { val arr = JSONArray() @@ -86,6 +87,56 @@ object AccountCache { } catch (e: Exception) { emptyList() } } + fun saveFahipay(context: Context, accounts: List) { + val arr = JSONArray() + for (acc in accounts) { + arr.put(JSONObject().apply { + put("profileName", acc.profileName) + put("profileType", acc.profileType) + put("accountNumber", acc.accountNumber) + put("accountBriefName", acc.accountBriefName) + put("currencyName", acc.currencyName) + put("accountTypeName", acc.accountTypeName) + put("availableBalance", acc.availableBalance) + put("currentBalance", acc.currentBalance) + put("blockedAmount", acc.blockedAmount) + put("mvrBalance", acc.mvrBalance) + put("statusDesc", acc.statusDesc) + put("loginTag", acc.loginTag) + put("internalId", acc.internalId) + }) + } + context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + .edit().putString(KEY_FAHIPAY, CacheEncryption.encrypt(arr.toString())).apply() + } + + fun loadFahipay(context: Context): List { + val raw = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + .getString(KEY_FAHIPAY, null) ?: return emptyList() + return try { + val arr = JSONArray(CacheEncryption.decrypt(raw)) + (0 until arr.length()).map { i -> + val o = arr.getJSONObject(i) + MibAccount( + profileName = o.optString("profileName"), + profileType = o.optString("profileType"), + accountNumber = o.optString("accountNumber"), + accountBriefName = o.optString("accountBriefName"), + currencyName = o.optString("currencyName"), + accountTypeName = o.optString("accountTypeName"), + availableBalance = o.optString("availableBalance"), + currentBalance = o.optString("currentBalance"), + blockedAmount = o.optString("blockedAmount"), + mvrBalance = o.optString("mvrBalance"), + statusDesc = o.optString("statusDesc"), + profileImageHash = null, + loginTag = o.optString("loginTag"), + internalId = o.optString("internalId", "") + ) + } + } catch (_: Exception) { emptyList() } + } + fun clear(context: Context) { context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().clear().apply() } diff --git a/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt b/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt index 7b048d2..8457c69 100644 --- a/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt +++ b/app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt @@ -19,11 +19,13 @@ class CredentialStore(context: Context) { data class MibCredentials(val username: String, val passwordHash: String, val otpSeed: String) data class BmlCredentials(val username: String, val password: String, val otpSeed: String) + data class FahipayCredentials(val idCard: String, val password: String) // ── MIB login credentials ───────────────────────────────────────────────── fun hasMibCredentials(): Boolean = prefs.contains("mib_enc_username") fun hasBmlCredentials(): Boolean = prefs.contains("bml_enc_username") + fun hasFahipayCredentials(): Boolean = prefs.contains("fahipay_enc_id_card") fun saveMibCredentials(username: String, passwordHash: String, otpSeed: String) { val key = getOrCreateKey() @@ -144,6 +146,114 @@ class CredentialStore(context: Context) { .apply() } + // ── Fahipay login credentials ───────────────────────────────────────────── + + fun saveFahipayCredentials(idCard: String, password: String) { + val key = getOrCreateKey() + prefs.edit() + .putString("fahipay_enc_id_card", encrypt(idCard, key)) + .putString("fahipay_enc_password", encrypt(password, key)) + .apply() + } + + fun loadFahipayCredentials(): FahipayCredentials? { + val key = getOrCreateKey() + val encId = prefs.getString("fahipay_enc_id_card", null) ?: return null + val encPw = prefs.getString("fahipay_enc_password", null) ?: return null + return try { + FahipayCredentials(decrypt(encId, key), decrypt(encPw, key)) + } catch (_: Exception) { null } + } + + fun clearFahipayCredentials() { + prefs.edit() + .remove("fahipay_enc_id_card") + .remove("fahipay_enc_password") + .apply() + } + + // ── Fahipay session (authId + __Secure-sess cookie) ─────────────────────── + + fun saveFahipaySession(authId: String, sessionCookie: String) { + val key = getOrCreateKey() + prefs.edit() + .putString("fahipay_enc_auth_id", encrypt(authId, key)) + .putString("fahipay_enc_session_cookie", encrypt(sessionCookie, key)) + .apply() + } + + fun loadFahipaySession(): Pair? { + val key = getOrCreateKey() + val encAuth = prefs.getString("fahipay_enc_auth_id", null) ?: return null + val encCookie = prefs.getString("fahipay_enc_session_cookie", null) ?: return null + return try { + Pair(decrypt(encAuth, key), decrypt(encCookie, key)) + } catch (_: Exception) { null } + } + + fun clearFahipaySession() { + prefs.edit() + .remove("fahipay_enc_auth_id") + .remove("fahipay_enc_session_cookie") + .apply() + } + + // ── Fahipay device UUID (generated once, shared across all Fahipay accounts) ─ + + fun getOrCreateFahipayDeviceUuid(): String { + val key = getOrCreateKey() + val enc = prefs.getString("fahipay_enc_device_uuid", null) + if (enc != null) { + try { return decrypt(enc, key) } catch (_: Exception) {} + } + val uuid = sh.sar.basedbank.api.fahipay.FahipayLoginFlow.generateDeviceUuid() + prefs.edit().putString("fahipay_enc_device_uuid", encrypt(uuid, key)).apply() + return uuid + } + + // ── Fahipay user profile ────────────────────────────────────────────────── + + data class FahipayUserProfile( + val fullName: String, + val email: String, + val mobile: String, + val nid: String, + val profileId: String, + val walletAccount: String, + val linkedAccounts: String + ) + + fun saveFahipayUserProfile(p: FahipayUserProfile) { + val json = org.json.JSONObject().apply { + put("fullName", p.fullName) + put("email", p.email) + put("mobile", p.mobile) + put("nid", p.nid) + put("profileId", p.profileId) + put("walletAccount", p.walletAccount) + put("linkedAccounts", p.linkedAccounts) + }.toString() + val key = getOrCreateKey() + prefs.edit().putString("fahipay_enc_profile", encrypt(json, key)).apply() + } + + fun loadFahipayUserProfile(): FahipayUserProfile? { + val key = getOrCreateKey() + val enc = prefs.getString("fahipay_enc_profile", null) ?: return null + return try { + val o = org.json.JSONObject(decrypt(enc, key)) + FahipayUserProfile( + fullName = o.optString("fullName"), + email = o.optString("email"), + mobile = o.optString("mobile"), + nid = o.optString("nid"), + profileId = o.optString("profileId"), + walletAccount = o.optString("walletAccount"), + linkedAccounts = o.optString("linkedAccounts", "{}") + ) + } catch (_: Exception) { null } + } + // ── Security credential (PIN / pattern hash) ────────────────────────────── /** diff --git a/app/src/main/res/drawable/fahipay_logo.png b/app/src/main/res/drawable/fahipay_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5538549a4ffbc1714407647955e7da2a03bb012c GIT binary patch literal 163168 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%0A`pZt3(E(hVeDvSk=TuA=lN-|&7UcaR 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�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 UI^>+G27!Od^6ICuPMTc*e}8UGHUIzs literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/fahipay_logo_long.xml b/app/src/main/res/drawable/fahipay_logo_long.xml new file mode 100644 index 0000000..c1ed775 --- /dev/null +++ b/app/src/main/res/drawable/fahipay_logo_long.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_bank_selection.xml b/app/src/main/res/layout/fragment_bank_selection.xml index 714c688..cf7ea5c 100644 --- a/app/src/main/res/layout/fragment_bank_selection.xml +++ b/app/src/main/res/layout/fragment_bank_selection.xml @@ -117,5 +117,49 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_credentials.xml b/app/src/main/res/layout/fragment_credentials.xml index d6ac577..21224c3 100644 --- a/app/src/main/res/layout/fragment_credentials.xml +++ b/app/src/main/res/layout/fragment_credentials.xml @@ -42,6 +42,7 @@ android:layout_marginBottom="32dp" /> + + + + + + + + Faisanet Mobile Banking Bank of Maldives BML Internet Banking + Fahipay + Digital Wallet + Enter your Fahipay ID card number and password. + ID Card Number + Authenticator Code (6 digits) + Enter the code from your authenticator app + Verify Sign In Enter your Maldives Islamic Bank credentials. Enter your Bank of Maldives credentials. diff --git a/docs/fahipayapi/01-login.md b/docs/fahipayapi/01-login.md new file mode 100644 index 0000000..63c0010 --- /dev/null +++ b/docs/fahipayapi/01-login.md @@ -0,0 +1,152 @@ +# Login + +Authenticate a user with their Fahipay ID card number and password. + +--- + +## Endpoint + +``` +POST https://fahipay.mv/api/app/login/ +``` + +--- + +## Request + +**Content-Type:** `multipart/form-data` + +### Form Fields + +| Field | Value | Notes | +|---|---|---| +| `email` | `A123456` | The user's national ID card number (e.g. `A123456`) | +| `password` | `••••••••••••••` | The user's Fahipay password | +| `grant_type` | `auth_id` | Always `auth_id` | +| `lang` | `en` | Always `en` | +| `version` | `2.0.0` | App version string | +| `platform` | `BasedBank` | Client identifier (original app sends `app`) | +| `device[available]` | `true` | See [common device fields](README.md#common-form-fields-device-info) | +| `device[platform]` | `Android` | | +| `device[uuid]` | `a1b2c3d4e5f60718` | Persistent 16-char hex UUID, generated once per install | +| `device[model]` | `22101320I` | `Build.MODEL` | +| `device[manufacturer]` | `Xiaomi` | `Build.MANUFACTURER` | +| `device[isVirtual]` | `false` | | +| `device[serial]` | `unknown` | | + +> **Note:** The field name is `email` but the value is the ID card number, not an email address. + +--- + +## curl Example + +```bash +curl --request POST \ + --url https://fahipay.mv/api/app/login/ \ + --compressed \ + --header 'accept: application/json' \ + --header 'accept-encoding: gzip, deflate, br' \ + --header 'connection: keep-alive' \ + --header 'user-agent: Mozilla/5.0 (Linux; Android 14; 22101320I Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36' \ + --form 'email=A123456' \ + --form 'password=your_password' \ + --form 'grant_type=auth_id' \ + --form 'lang=en' \ + --form 'version=2.0.0' \ + --form 'platform=BasedBank' \ + --form 'device[available]=true' \ + --form 'device[platform]=Android' \ + --form 'device[uuid]=a1b2c3d4e5f60718' \ + --form 'device[model]=22101320I' \ + --form 'device[manufacturer]=Xiaomi' \ + --form 'device[isVirtual]=false' \ + --form 'device[serial]=unknown' +``` + +--- + +## Responses + +### Success — 2FA required + +The user has TOTP two-factor authentication enabled. Proceed to the [OTP step](02-otp.md). + +```json +{ + "two_factor_required": true, + "two_factor_method": "totp", + "title": "Success", + "msg": "You are now logged in.", + "type": "success" +} +``` + +| Field | Type | Description | +|---|---|---| +| `two_factor_required` | `bool` | `true` — must call `/api/app/otp/` next | +| `two_factor_method` | `string` | `"totp"` — standard TOTP (RFC 6238) | +| `type` | `string` | `"success"` on success, `"error"` on failure | + +The server sets the `__Secure-sess` session cookie on this response. It must be included in all subsequent requests. + +--- + +### Success — No 2FA + +The user does not have 2FA enabled. The `authID` is returned directly — no OTP step needed. + +```json +{ + "two_factor_required": false, + "authID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "title": "Success", + "msg": "You are now logged in.", + "type": "success" +} +``` + +| Field | Type | Description | +|---|---|---| +| `two_factor_required` | `bool` | `false` — login is complete | +| `authID` | `string` | 40-char hex token; use as `authid` header for all subsequent requests | + +--- + +### Failure + +```json +{ + "title": "Error", + "msg": "Invalid credentials", + "type": "error" +} +``` + +`type` is `"error"` and `msg` contains a human-readable reason. + +--- + +## Session Cookie + +The `__Secure-sess` cookie is set by the server on the first response and must be sent on every subsequent request. It is a standard HTTP cookie with the `Secure` flag. + +``` +Set-Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; Path=/; Secure; HttpOnly; SameSite=Strict +``` + +Store both the cookie value and the `authID` together to represent a persisted session. + +--- + +## Next Steps + +- If `two_factor_required` is `true` → proceed to **[OTP / 2FA](02-otp.md)** +- If `two_factor_required` is `false` → skip to **[Profile](03-profile.md)** + +--- + +  + +--- + +[← README](README.md)     **Next →** [OTP / 2FA](02-otp.md) diff --git a/docs/fahipayapi/02-otp.md b/docs/fahipayapi/02-otp.md new file mode 100644 index 0000000..3ac81e9 --- /dev/null +++ b/docs/fahipayapi/02-otp.md @@ -0,0 +1,158 @@ +# OTP / 2FA Verification + +Submit a TOTP code to complete login when `two_factor_required` was `true` in the [login response](01-login.md). + +--- + +## Endpoint + +``` +POST https://fahipay.mv/api/app/otp/ +``` + +--- + +## Prerequisites + +- Completed the [login step](01-login.md) and received `two_factor_required: true` +- The `__Secure-sess` session cookie from the login response must be present +- A valid TOTP code from the user's authenticator app + +--- + +## Request + +**Content-Type:** `multipart/form-data` + +### Form Fields + +| Field | Value | Notes | +|---|---|---| +| `code` | `123456` | 6-digit TOTP code from the user's authenticator app | +| `channel` | `totp` | Always `totp` | +| `action` | `login` | Always `login` for the login flow | +| `grant_type` | `auth_id` | Always `auth_id` | +| `lang` | `en` | Always `en` | +| `version` | `2.0.0` | App version string | +| `platform` | `BasedBank` | Client identifier | +| `device[available]` | `true` | Same device fields as login — must match | +| `device[platform]` | `Android` | | +| `device[uuid]` | `a1b2c3d4e5f60718` | Must be the **same UUID** used in the login request | +| `device[model]` | `22101320I` | | +| `device[manufacturer]` | `Xiaomi` | | +| `device[isVirtual]` | `false` | | +| `device[serial]` | `unknown` | | + +> The `device[uuid]` must be identical to the one sent in the login request. The server uses this to tie the OTP challenge to the login attempt. + +--- + +## curl Example + +```bash +curl --request POST \ + --url https://fahipay.mv/api/app/otp/ \ + --compressed \ + --header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'accept: application/json' \ + --header 'accept-encoding: gzip, deflate, br' \ + --header 'connection: keep-alive' \ + --header 'user-agent: Mozilla/5.0 (Linux; Android 14; 22101320I Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36' \ + --form 'code=123456' \ + --form 'channel=totp' \ + --form 'action=login' \ + --form 'grant_type=auth_id' \ + --form 'lang=en' \ + --form 'version=2.0.0' \ + --form 'platform=BasedBank' \ + --form 'device[available]=true' \ + --form 'device[platform]=Android' \ + --form 'device[uuid]=a1b2c3d4e5f60718' \ + --form 'device[model]=22101320I' \ + --form 'device[manufacturer]=Xiaomi' \ + --form 'device[isVirtual]=false' \ + --form 'device[serial]=unknown' +``` + +--- + +## Responses + +### Success + +```json +{ + "title": "Success", + "authID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "msg": "Code verification successful", + "type": "success" +} +``` + +| Field | Type | Description | +|---|---|---| +| `authID` | `string` | 40-char hex token — use as `authid` header for all subsequent requests | +| `type` | `string` | `"success"` | +| `msg` | `string` | Human-readable confirmation | + +--- + +### Failure — Wrong code + +```json +{ + "title": "Error", + "msg": "Invalid OTP code", + "type": "error" +} +``` + +--- + +### Failure — Expired / session mismatch + +```json +{ + "title": "Error", + "msg": "Session expired. Please login again.", + "type": "error" +} +``` + +If the session cookie has expired or the UUID does not match, re-run the full login flow from [Step 1](01-login.md). + +--- + +## TOTP Details + +Fahipay uses standard RFC 6238 TOTP: + +| Parameter | Value | +|---|---| +| Algorithm | HMAC-SHA1 | +| Period | 30 seconds | +| Digits | 6 | +| Encoding | Base32 secret | + +The user's TOTP seed is set up during initial Fahipay account creation and is the same secret used in any standard authenticator app (Google Authenticator, Aegis, etc.). + +--- + +## Storing the Session + +After receiving `authID`, persist both values for future sessions: + +| Value | Description | +|---|---| +| `authID` | 40-char hex token — send as `authid` header | +| `__Secure-sess` | Cookie value — send as `Cookie: __Secure-sess=` | + +On app restart, attempt requests with the stored session before falling back to a full re-login. + +--- + +  + +--- + +[← Login](01-login.md)     **Next →** [Profile](03-profile.md) diff --git a/docs/fahipayapi/03-profile.md b/docs/fahipayapi/03-profile.md new file mode 100644 index 0000000..5800765 --- /dev/null +++ b/docs/fahipayapi/03-profile.md @@ -0,0 +1,229 @@ +# User Profile + +Fetch the authenticated user's full profile, including personal details, linked bank accounts, wallet settings, and permissions. + +--- + +## Endpoint + +``` +GET https://fahipay.mv/actions/getprofile/?lang=en +``` + +--- + +## Prerequisites + +- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md) +- Valid `__Secure-sess` session cookie + +--- + +## Request + +### Headers + +| Header | Value | +|---|---| +| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | +| `content-type` | `multipart/form-data` | +| `User-Agent` | `okhttp/4.12.0` | +| `Accept-Encoding` | `gzip` | +| `Connection` | `Keep-Alive` | +| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | + +--- + +## curl Example + +```bash +curl --request GET \ + --url 'https://fahipay.mv/actions/getprofile/?lang=en' \ + --compressed \ + --header 'Accept-Encoding: gzip' \ + --header 'Connection: Keep-Alive' \ + --header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'User-Agent: okhttp/4.12.0' \ + --header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'content-type: multipart/form-data' +``` + +--- + +## Response + +```json +{ + "nidexpiry": "2027-06-01", + "about": "", + "email": "user@example.com", + "country": "Maldives", + "fullname": "Mohamed Ali", + "postcode": "", + "props": { + "accs": { + "bml": { + "mvr": "7730000000001", + "usd": "7730000000002", + "mvr2": "7770000000003" + }, + "cbm": { + "mvr": "1000000001" + }, + "mib": { + "mvr": "90101000000001000", + "usd": "90101000000002000" + }, + "sbi": { + "mvr": "12600000000001" + } + }, + "lang": "en", + "wsize": 5000, + "withdraw": { + "fee": "3", + "unit": "%", + "freelimit": 0 + }, + "IDverified": 1, + "thresholds": { + "amount": 0, + "countries": { + "list": [], + "mode": "deny" + }, + "frequency": 0 + }, + "amountTopup": 1, + "receiptTopup": 1, + "verifiedWith": "efaas", + "notifications": { + "txn": ["push"], + "login": ["push", "email"], + "balance": ["push"] + }, + "allowedActions": [ + "withdraw", + "payment", + "topup", + "transfer" + ], + "two_factor_enabled": 1, + "efaas_login_enabled": 0, + "acc": "500000000001", + "walletType": "basic" + }, + "mobile": "9600000001", + "city": "101", + "nid": "A123456", + "level": "1", + "address": "Example Address", + "accs": { + "bml": [ + { "name": "MOHAMED ALI" } + ] + }, + "invitecode": "XXXXX", + "profileID": "0000", + "verificationUploadMethods": ["camera", "file"], + "faceVerificationRequired": true, + "p2pqr": "https://fahipay.mv/api/qrcode/?data=9600000001", + "smsAuth": "xxxxxxxxxxxxxxxxxxxx", + "type": "success" +} +``` + +--- + +## Key Fields + +### Top-level + +| Field | Type | Description | +|---|---|---| +| `fullname` | `string` | User's full name | +| `email` | `string` | Registered email address | +| `mobile` | `string` | Registered mobile number | +| `nid` | `string` | National ID card number (e.g. `A239225`) | +| `nidexpiry` | `string` | NID expiry date (`YYYY-MM-DD`) | +| `profileID` | `string` | Fahipay internal numeric user ID — use in `loginTag` | +| `level` | `string` | Account verification level | +| `country` | `string` | Registered country | +| `city` | `string` | City code | +| `address` | `string` | Street address | +| `invitecode` | `string` | Referral invite code | +| `p2pqr` | `string` | URL to this user's P2P QR code image | +| `type` | `string` | `"success"` or `"error"` | + +--- + +### `props` Object + +| Field | Type | Description | +|---|---|---| +| `acc` | `string` | The user's Fahipay wallet account number | +| `walletType` | `string` | `"basic"` or `"premium"` | +| `wsize` | `number` | Wallet size / transaction limit | +| `two_factor_enabled` | `number` | `1` if TOTP 2FA is active | +| `efaas_login_enabled` | `number` | `1` if eFaas login is enabled | +| `IDverified` | `number` | `1` if identity is verified | +| `allowedActions` | `string[]` | Permitted operations: `withdraw`, `payment`, `topup`, `transfer` | + +> `props.acc` is the wallet account number shown in the app and used as the primary account identifier. + +--- + +### `props.accs` — Linked Bank Accounts + +Contains the user's bank accounts linked to Fahipay, organised by bank code. Used when topping up or withdrawing via linked banks. + +| Key | Bank | +|---|---| +| `bml` | Bank of Maldives | +| `mib` | Maldives Islamic Bank | +| `cbm` | Central Bank of Maldives | +| `sbi` | State Bank of India | + +Each bank entry is an object of named account numbers: + +```json +"bml": { + "mvr": "7730000145458", + "usd": "7730000199959", + "mvr2": "7770000045775" +} +``` + +Store the raw JSON of `props.accs` — it is needed to determine the source account when initiating top-ups or withdrawals. + +--- + +### `props.withdraw` + +| Field | Description | +|---|---| +| `fee` | Withdrawal fee amount | +| `unit` | Fee unit — `%` = percentage, otherwise fixed | +| `freelimit` | Free withdrawal limit (0 = no free limit) | + +--- + +## Error Response + +```json +{ + "title": "Error", + "msg": "Unauthorized", + "type": "error" +} +``` + +If the `authID` is invalid or expired, re-run the full [login flow](01-login.md). + +--- + +  + +--- + +[← OTP / 2FA](02-otp.md)     **Next →** [Balance](04-balance.md) diff --git a/docs/fahipayapi/04-balance.md b/docs/fahipayapi/04-balance.md new file mode 100644 index 0000000..2543cca --- /dev/null +++ b/docs/fahipayapi/04-balance.md @@ -0,0 +1,109 @@ +# Wallet Balance + +Fetch the current balance of the authenticated user's Fahipay wallet. + +--- + +## Endpoint + +``` +GET https://fahipay.mv/actions/getbalance/?lang=en +``` + +--- + +## Prerequisites + +- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md) +- Valid `__Secure-sess` session cookie + +--- + +## Request + +### Headers + +| Header | Value | +|---|---| +| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | +| `content-type` | `multipart/form-data` | +| `User-Agent` | `okhttp/4.12.0` | +| `Accept-Encoding` | `gzip` | +| `Connection` | `Keep-Alive` | +| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | + +### Query Parameters + +| Parameter | Value | Description | +|---|---|---| +| `lang` | `en` | Language — always `en` | + +--- + +## curl Example + +```bash +curl --request GET \ + --url 'https://fahipay.mv/actions/getbalance/?lang=en' \ + --compressed \ + --header 'Accept-Encoding: gzip' \ + --header 'Connection: Keep-Alive' \ + --header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'User-Agent: okhttp/4.12.0' \ + --header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'content-type: multipart/form-data' +``` + +--- + +## Response + +### Success + +```json +{ + "balance": 1.01, + "rewards": "0", + "error": false, + "type": "success" +} +``` + +| Field | Type | Description | +|---|---|---| +| `balance` | `number` | Current wallet balance in MVR | +| `rewards` | `string` | Rewards/cashback points balance | +| `error` | `bool` | `false` on success | +| `type` | `string` | `"success"` | + +> All Fahipay wallet balances are in **MVR** (Maldivian Rufiyaa). There is no multi-currency wallet. + +--- + +### Error + +```json +{ + "error": true, + "type": "error", + "msg": "Unauthorized" +} +``` + +If `error` is `true` or `type` is `"error"`, the session is invalid. Re-run the [login flow](01-login.md). + +--- + +## Notes + +- This endpoint only returns the Fahipay wallet balance, not any linked bank account balances. +- The `rewards` field is returned as a string even though it represents a numeric value. Parse it with `toDoubleOrNull()`. +- Call this endpoint after [fetching the profile](03-profile.md) to construct the full account object with both account number and balance. + +--- + +  + +--- + +[← Profile](03-profile.md)     **Next →** [Transaction History](05-history.md) diff --git a/docs/fahipayapi/05-history.md b/docs/fahipayapi/05-history.md new file mode 100644 index 0000000..4968b1f --- /dev/null +++ b/docs/fahipayapi/05-history.md @@ -0,0 +1,250 @@ +# Transaction History + +Fetch the user's paginated wallet activity log. Each entry represents a single transaction: top-up, payment, transfer, or withdrawal. + +--- + +## Endpoint + +``` +GET https://fahipay.mv/actions/activity/?s={start}&l={limit}&lang=en +``` + +--- + +## Prerequisites + +- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md) +- Valid `__Secure-sess` session cookie + +--- + +## Request + +### Headers + +| Header | Value | +|---|---| +| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | +| `content-type` | `multipart/form-data` | +| `User-Agent` | `okhttp/4.12.0` | +| `Accept-Encoding` | `gzip` | +| `Connection` | `Keep-Alive` | +| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | + +### Query Parameters + +| Parameter | Description | Example | +|---|---|---| +| `s` | Start offset (0-based) | `0`, `15`, `30` | +| `l` | Number of entries to return per page | `15` | +| `lang` | Language | `en` | + +--- + +## Pagination + +The API uses offset-based pagination via the `s` (start) and `l` (limit) parameters. + +| Page | URL | +|---|---| +| First | `/actions/activity/?s=0&l=15&lang=en` | +| Second | `/actions/activity/?s=15&l=15&lang=en` | +| Third | `/actions/activity/?s=30&l=15&lang=en` | +| N-th | `/actions/activity/?s={(N-1)*15}&l=15&lang=en` | + +The response includes a `total` count and a `next` URL. Stop fetching when: +- The returned `entries` array is empty, **or** +- `s + entries.length >= total` + +--- + +## curl Examples + +### Page 1 + +```bash +curl --request GET \ + --url 'https://fahipay.mv/actions/activity/?s=0&l=15&lang=en' \ + --compressed \ + --header 'Accept-Encoding: gzip' \ + --header 'Connection: Keep-Alive' \ + --header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'User-Agent: okhttp/4.12.0' \ + --header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'content-type: multipart/form-data' +``` + +### Page 2 + +```bash +curl --request GET \ + --url 'https://fahipay.mv/actions/activity/?s=15&l=15&lang=en' \ + --compressed \ + --header 'Accept-Encoding: gzip' \ + --header 'Connection: Keep-Alive' \ + --header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'User-Agent: okhttp/4.12.0' \ + --header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'content-type: multipart/form-data' +``` + +--- + +## Response + +```json +{ + "entries": [ + { + "date": "2026-05-16 15:10:25", + "name": "Cash Deposit", + "details": "Transferred Via BML ebanking", + "icon": "https://fahipay.mv/images/app/bml.png", + "transaction": "FP20260101120000XXXX", + "type": "topup", + "amount": 0.01, + "success": 1, + "status": "Success" + }, + { + "date": "2026-03-01 10:00:00", + "name": "Fitr Zakat Payment", + "details": "Payment for Fitr Zakat - 1447", + "icon": "https://fahipay.mv/images/app/zakat_service.png", + "transaction": "FP20260301100000XXXX", + "type": "payment", + "subtype": "FTZKT", + "data": { + "nid": "A123456", + "name": "Mohamed Ali", + "categories": [ + { + "type": 7, + "count": 1, + "price": "10.00", + "name": "Normal Wheet" + } + ], + "sadaqat": "0.00" + }, + "amount": -10, + "success": 1, + "status": "Success" + }, + { + "date": "2026-02-01 09:00:00", + "name": "Ooredoo Raastas", + "details": "Mobile Recharge - 9600000001", + "icon": "https://fahipay.mv/images/app/ooredoo.png", + "transaction": "FP20260201090000XXXX", + "type": "payment", + "subtype": "OORCH", + "amount": -100, + "success": 1, + "status": "Success" + } + ], + "total": 42, + "next": "https://fahipay.mv/actions/activity/?s=15&l=15", + "type": "success" +} +``` + +--- + +## Response Fields + +### Top-level + +| Field | Type | Description | +|---|---|---| +| `entries` | `array` | List of transaction entries for this page | +| `total` | `number` | Total number of transactions across all pages | +| `next` | `string` | URL of the next page (`null` or absent on last page) | +| `type` | `string` | `"success"` | + +--- + +### Entry Object + +| Field | Type | Description | +|---|---|---| +| `date` | `string` | Transaction date/time — format: `YYYY-MM-DD HH:mm:ss` | +| `name` | `string` | Human-readable transaction name (e.g. `"Cash Deposit"`, `"Ooredoo Raastas"`) | +| `details` | `string` | Secondary description (e.g. `"Transferred Via BML ebanking"`, `"Mobile Recharge - 9198026"`) | +| `icon` | `string` | URL of the merchant/bank icon image | +| `transaction` | `string` | Unique transaction reference ID (e.g. `FP20260516151002ZXGD`) | +| `type` | `string` | Transaction category — see table below | +| `subtype` | `string` | Optional service-specific subtype code (e.g. `OORCH`, `DHBPY`) | +| `amount` | `number` | Transaction amount in MVR — **negative = debit, positive = credit** | +| `success` | `number` | `1` = successful, `0` = failed | +| `status` | `string` | Human-readable status string (e.g. `"Success"`, `"Failed"`) | +| `data` | `object` | Optional. Present on some payment types with extra metadata | + +--- + +### Transaction Types (`type` field) + +| Value | Description | +|---|---| +| `topup` | Money deposited into the wallet (credit, positive amount) | +| `payment` | Money paid out for a service (debit, negative amount) | +| `transfer` | Peer-to-peer transfer to/from another Fahipay user | +| `withdraw` | Money withdrawn from the wallet to a bank account | + +--- + +### Known Subtypes (`subtype` field) + +| Code | Service | +|---|---| +| `OORCH` | Ooredoo Raastas (mobile top-up) | +| `OOBPY` | Ooredoo BillPay | +| `DHRCH` | Dhiraagu Reload (mobile top-up) | +| `DHBPY` | Dhiraagu BillPay | +| `DHPKG` | Dhiraagu Package (data package) | +| `FTZKT` | Fitr Zakat Payment | + +--- + +### Transaction ID Format + +``` +FP + YYYYMMDDHHMMSS + XXXX +``` + +Example: `FP20260101120000XXXX` +- `FP` — Fahipay prefix +- `20260101` — date (2026-01-01) +- `120000` — time (12:00:00) +- `XXXX` — 4-char random suffix + +--- + +## Amount Sign Convention + +| Sign | Meaning | +|---|---| +| Positive (`+`) | Credit — money received (top-up, incoming transfer) | +| Negative (`-`) | Debit — money spent (payment, withdrawal, outgoing transfer) | + +--- + +## Date Format + +All dates are in local Maldives time (UTC+5), formatted as: + +``` +YYYY-MM-DD HH:mm:ss +``` + +Example: `2026-05-16 15:10:25` + +--- + +  + +--- + +[← Balance](04-balance.md)     **Next →** [Profile Picture](06-profile-picture.md) diff --git a/docs/fahipayapi/06-profile-picture.md b/docs/fahipayapi/06-profile-picture.md new file mode 100644 index 0000000..a0c9c9e --- /dev/null +++ b/docs/fahipayapi/06-profile-picture.md @@ -0,0 +1,117 @@ +# Profile Picture + +Fetch the authenticated user's profile picture. The endpoint redirects to the actual image URL. + +--- + +## Endpoint + +``` +GET https://fahipay.mv/images/profiles/picture/?t={timestamp} +``` + +--- + +## Prerequisites + +- Valid `authID` from [login](01-login.md) or [OTP](02-otp.md) +- Valid `__Secure-sess` session cookie + +--- + +## Request + +### Headers + +| Header | Value | +|---|---| +| `authid` | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | +| `User-Agent` | `okhttp/4.12.0` | +| `Accept-Encoding` | `gzip` | +| `Connection` | `Keep-Alive` | +| `Cookie` | `__Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | + +### Query Parameters + +| Parameter | Description | Example | +|---|---|---| +| `t` | Cache-busting timestamp string | `Sat May 16 2026 14:57:52 GMT+0500` | + +The `t` parameter is a URL-encoded timestamp used to prevent browser caching. The value can be any string — the server ignores it for routing purposes. + +--- + +## curl Example + +```bash +curl --request GET \ + --url 'https://fahipay.mv/images/profiles/picture/?t=Sat%20Jan%2001%202026%2012:00:00%20GMT+0500' \ + --compressed \ + --header 'Accept-Encoding: gzip' \ + --header 'Connection: Keep-Alive' \ + --header 'Cookie: __Secure-sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ + --header 'User-Agent: okhttp/4.12.0' \ + --header 'authid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +``` + +--- + +## Response + +### Success + +The server responds with `HTTP 302` and a `Location` header pointing to the actual image URL. + +``` +HTTP/1.1 302 Found +Location: https://fahipay.mv/images/profiles/0000/avatar.jpg?v=0000000000 +``` + +Follow the redirect to download the image. The final response is the raw image bytes (`image/jpeg` or `image/png`). + +--- + +### No Picture Set + +If the user has not uploaded a profile picture, the redirect points to a default placeholder image: + +``` +Location: https://fahipay.mv/images/profiles/default.png +``` + +--- + +### Error + +If the session is invalid, the server returns `HTTP 401` or redirects to an error page. + +--- + +## Implementation Notes + +- HTTP clients that follow redirects automatically (e.g. `OkHttpClient` with `followRedirects(true)`) will return the image bytes directly. +- Use `followRedirects(false)` and read the `Location` header if you need the resolved image URL separately. +- The image URL contains the user's `profileID` in the path — this matches the `profileID` field from the [profile response](03-profile.md). +- The `v=` query parameter in the image URL is a version/cache key. It changes when the user updates their picture. + +--- + +## Suggested Usage + +``` +timestamp = current time formatted as URL-safe string +GET /images/profiles/picture/?t={timestamp} + → 302 Location: + → GET + → image bytes +``` + +Cache the downloaded image by `profileID` and re-fetch when the user explicitly refreshes, rather than on every app launch. + +--- + +  + +--- + +[← Transaction History](05-history.md) diff --git a/docs/fahipayapi/README.md b/docs/fahipayapi/README.md new file mode 100644 index 0000000..b2e8c9b --- /dev/null +++ b/docs/fahipayapi/README.md @@ -0,0 +1,129 @@ +# Fahipay API Documentation + +Reverse-engineered from traffic captures of the Fahipay Android WebView app (`fahipay.mv`). + +--- + +## Overview + +Fahipay is a Maldivian digital wallet service. The API uses a mix of `multipart/form-data` POST requests for authentication and simple authenticated `GET` requests for data retrieval. + +Authentication is session-based: +- A `__Secure-sess` cookie is set by the server on first contact and must be sent with every request. +- After login (and optional TOTP verification), the server returns an `authID` token that must be sent as an `authid` header with every subsequent request. + +--- + +## Base URL + +``` +https://fahipay.mv +``` + +--- + +## Authentication Model + +| Value | How obtained | How used | +|---|---|---| +| `__Secure-sess` cookie | Set by server on first request | Sent automatically via cookie jar | +| `authID` | Returned by `/api/app/login/` or `/api/app/otp/` | Sent as `authid: ` header | + +Both must be present on every authenticated request. + +--- + +## Common Request Headers + +### Login / OTP endpoints +``` +Content-Type: multipart/form-data; boundary= +accept: application/json +accept-encoding: gzip, deflate, br +connection: keep-alive +user-agent: Mozilla/5.0 (Linux; Android 14; 22101320I Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36 +``` + +### Authenticated data endpoints +``` +Accept-Encoding: gzip +Connection: Keep-Alive +User-Agent: okhttp/4.12.0 +authid: +content-type: multipart/form-data +``` + +--- + +## Common Form Fields (Device Info) + +All login and OTP requests include a standard set of device fields: + +| Field | Example value | Notes | +|---|---|---| +| `device[available]` | `true` | Always `true` | +| `device[platform]` | `Android` | Always `Android` | +| `device[uuid]` | `a1b2c3d4e5f60718` | 16 hex chars, generated once per install, persisted | +| `device[model]` | `22101320I` | Device model string | +| `device[manufacturer]` | `Xiaomi` | Device manufacturer | +| `device[isVirtual]` | `false` | Always `false` | +| `device[serial]` | `unknown` | Always `unknown` | + +The `device[uuid]` must be consistent across all requests from the same install. Generate it once and store it permanently. + +--- + +## Login Flow + +``` +Client Server + | | + | POST /api/app/login/ | + | { email=IDCARD, password, ... } | + |---------------------------------->| + | { two_factor_required: bool } | + |<----------------------------------| + | | + | (if two_factor_required=true) | + | POST /api/app/otp/ | + | { code=TOTP, channel=totp, ... } | + |---------------------------------->| + | { authID: "..." } | + |<----------------------------------| + | | + | (if two_factor_required=false) | + | authID already in login response | + | | + | GET /actions/getprofile/ | + | authid: | + |---------------------------------->| + | { fullname, profileID, ... } | + |<----------------------------------| + | | + | GET /actions/getbalance/ | + | authid: | + |---------------------------------->| + | { balance: 1.01 } | + |<----------------------------------| +``` + +--- + +## Documents + +| # | File | Description | +|---|---|---| +| 1 | [Login](01-login.md) | Authenticate with ID card and password | +| 2 | [OTP / 2FA](02-otp.md) | TOTP verification when 2FA is enabled | +| 3 | [Profile](03-profile.md) | Fetch user profile and linked bank accounts | +| 4 | [Balance](04-balance.md) | Fetch wallet balance | +| 5 | [Transaction History](05-history.md) | Paginated activity/transaction history | +| 6 | [Profile Picture](06-profile-picture.md) | Fetch user profile picture | + +--- + +  + +--- + +> **Next →** [Login](01-login.md) diff --git a/docs/fahipayapi/fahipay_logo_long.svg b/docs/fahipayapi/fahipay_logo_long.svg new file mode 100644 index 0000000..f407799 --- /dev/null +++ b/docs/fahipayapi/fahipay_logo_long.svg @@ -0,0 +1 @@ + \ No newline at end of file