contacts, contact picker and trnasfer account look up ui

This commit is contained in:
2026-05-14 05:24:23 +05:00
parent c49cce0cf2
commit b452940ed0
19 changed files with 1284 additions and 35 deletions
@@ -263,7 +263,8 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
currentBalance = a.optString("currentBalance"),
blockedAmount = a.optString("blockedAmount"),
mvrBalance = a.optString("mvrBalance"),
statusDesc = a.optString("statusDesc")
statusDesc = a.optString("statusDesc"),
profileImageHash = profile.customerImage
)
)
}
@@ -283,11 +284,22 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
name = p.optString("name"),
cifType = p.optString("cifType"),
profileType = p.optString("profileType"),
color = p.optString("color")
color = p.optString("color"),
customerImage = p.optString("customerImage").takeIf { it.isNotBlank() }
)
}
}
/** Fetches a profile image via P41. Returns base64 JPEG string, or null if not found. */
fun fetchProfileImage(session: MibSession, imageHash: String): String? {
val payload = baseData(session, "P41").apply {
put("imageHash", imageHash)
}
val resp = doRequest(session, payload, "n")
if (!resp.optBoolean("success", false)) return null
return resp.optString("profileImage").takeIf { it.isNotBlank() }
}
private fun post(body: FormBody): String {
val request = Request.Builder()
.url(BASE_URL)
@@ -15,7 +15,8 @@ data class MibProfile(
val name: String,
val cifType: String,
val profileType: String,
val color: String
val color: String,
val customerImage: String?
)
data class MibAccount(
@@ -29,7 +30,8 @@ data class MibAccount(
val currentBalance: String,
val blockedAmount: String,
val mvrBalance: String,
val statusDesc: String
val statusDesc: String,
val profileImageHash: String?
)
data class MibBeneficiaryCategory(
@@ -53,6 +55,12 @@ data class MibBeneficiary(
val benefCategoryId: String // "0" = uncategorized
)
data class MibIpsAccountInfo(
val accountName: String,
val accountNumber: String,
val bankId: String
)
data class MibFinanceDeal(
val dealNo: String,
val productDesc: String,
@@ -0,0 +1,143 @@
package sh.sar.basedbank.api.mib
import android.util.Log
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import java.util.concurrent.TimeUnit
class MibLookupException(message: String) : Exception(message)
class MibTransferClient {
private val TAG = "MibTransferClient"
private val BASE_WV_URL = "https://faisamobilex-wv.mib.com.mv"
private val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
private fun cookieHeader(session: MibSession) =
"mbmodel=IOS-1.0; xxid=${session.xxid}; IBSID=${session.xxid}; " +
"mbnonce=${session.nonceGenerator}; time-tracker=597"
private fun Request.Builder.withWvHeaders(session: MibSession): Request.Builder = this
.header("Cookie", cookieHeader(session))
.header(
"User-Agent",
"Mozilla/5.0 (Linux; Android 14; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36"
)
.header("X-Requested-With", "XMLHttpRequest")
.header("Accept", "*/*")
.header("Origin", BASE_WV_URL)
.header("Referer", "$BASE_WV_URL/transfer/quick")
/**
* Routes the lookup to the correct endpoint based on input format:
*
* - 15 digits starting with 7 → IPS account lookup (BML / local bank)
* - 17 digits starting with 9 → MIB internal account name lookup
* - 7 digits starting with 7 or 9,
* A + 6 digits,
* or email address → Favara alias lookup
*/
fun lookup(session: MibSession, input: String): MibIpsAccountInfo {
val trimmed = input.trim()
return when {
trimmed.matches(Regex("^7\\d{12}$")) -> lookupIpsAccount(session, trimmed)
trimmed.matches(Regex("^9\\d{16}$")) -> lookupAccountName(session, trimmed)
trimmed.matches(Regex("^[79]\\d{6}$")) -> lookupAlias(session, trimmed)
trimmed.matches(Regex("^[Aa]\\d{6}$")) -> lookupAlias(session, trimmed)
trimmed.contains("@") -> lookupAlias(session, trimmed)
else -> throw MibLookupException("Unrecognized account number format")
}
}
/** BML / local bank: 15-digit account starting with 7. */
private fun lookupIpsAccount(session: MibSession, accountNumber: String): MibIpsAccountInfo {
val body = FormBody.Builder().add("benefAccount", accountNumber).build()
val request = Request.Builder()
.url("$BASE_WV_URL/AjaxAlias/getIPSAccount")
.post(body)
.withWvHeaders(session)
.build()
return client.newCall(request).execute().use { response ->
Log.d(TAG, "lookupIpsAccount: HTTP ${response.code}")
val bodyStr = response.body?.string() ?: ""
val json = try { JSONObject(bodyStr) } catch (_: Exception) { null }
if (!response.isSuccessful || json == null || !json.optBoolean("success")) {
throw MibLookupException(
json?.optString("reasonText")?.trim()?.takeIf { it.isNotBlank() }
?: "Request failed (${response.code})"
)
}
MibIpsAccountInfo(
accountName = json.optString("accountName").trim(),
accountNumber = accountNumber,
bankId = json.optString("bankBic")
)
}
}
/** MIB internal: 17-digit account starting with 9. */
private fun lookupAccountName(session: MibSession, accountNumber: String): MibIpsAccountInfo {
val body = FormBody.Builder().add("accountNo", accountNumber).build()
val request = Request.Builder()
.url("$BASE_WV_URL/ajaxBeneficiary/getAccountName")
.post(body)
.withWvHeaders(session)
.build()
return client.newCall(request).execute().use { response ->
Log.d(TAG, "lookupAccountName: HTTP ${response.code}")
val bodyStr = response.body?.string() ?: ""
val json = try { JSONObject(bodyStr) } catch (_: Exception) { null }
if (!response.isSuccessful || json == null || !json.optBoolean("success")) {
throw MibLookupException(
json?.optString("reasonText")?.trim()?.takeIf { it.isNotBlank() }
?: "Request failed (${response.code})"
)
}
// accountName may be at root or inside a "data" object
val name = json.optString("accountName").takeIf { it.isNotBlank() }
?: json.optJSONObject("data")?.optString("accountName") ?: ""
MibIpsAccountInfo(
accountName = name.trim(),
accountNumber = accountNumber,
bankId = "MADVMVMV" // MIB
)
}
}
/** Favara alias: 7-digit shortcode (7/9 prefix), A-format ID, or email. */
private fun lookupAlias(session: MibSession, aliasName: String): MibIpsAccountInfo {
val body = FormBody.Builder().add("aliasName", aliasName).build()
val request = Request.Builder()
.url("$BASE_WV_URL/AjaxAlias/getAlias")
.post(body)
.withWvHeaders(session)
.build()
return client.newCall(request).execute().use { response ->
Log.d(TAG, "lookupAlias: HTTP ${response.code}")
val bodyStr = response.body?.string() ?: ""
val json = try { JSONObject(bodyStr) } catch (_: Exception) { null }
if (!response.isSuccessful || json == null || !json.optBoolean("success")) {
throw MibLookupException(
json?.optString("reasonText")?.trim()?.takeIf { it.isNotBlank() }
?: "Request failed (${response.code})"
)
}
val data = json.getJSONObject("data")
val cdtrAcct = data.getJSONObject("CdtrAcct")
MibIpsAccountInfo(
accountName = data.optString("BfyNm").trim(),
accountNumber = cdtrAcct.optString("Acct"),
bankId = cdtrAcct.optString("FinInstnId")
)
}
}
}