add support for mib loans view

This commit is contained in:
2026-05-14 05:24:18 +05:00
parent d28687bc1c
commit 69b7862e65
15 changed files with 896 additions and 4 deletions
@@ -0,0 +1,82 @@
package sh.sar.basedbank.api.mib
import android.util.Log
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.TimeUnit
import kotlin.math.ceil
class MibFinancingClient {
private val TAG = "MibFinancingClient"
private val BASE_WV_URL = "https://faisamobilex-wv.mib.com.mv"
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
fun fetchFinancing(session: MibSession): List<MibFinanceDeal> {
val cookieHeader = "mbmodel=IOS-1.0; " +
"xxid=${session.xxid}; " +
"IBSID=${session.xxid}; " +
"mbnonce=${session.nonceGenerator}; " +
"time-tracker=597"
val request = Request.Builder()
.url("$BASE_WV_URL/financing?dashurl=1")
.header("Cookie", cookieHeader)
.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", "mv.com.mib.faisamobilex")
.get()
.build()
val html = client.newCall(request).execute().use { response ->
Log.d(TAG, "fetchFinancing: HTTP ${response.code}")
if (!response.isSuccessful) return emptyList()
response.body?.string() ?: return emptyList()
}
return parseFinancingHtml(html)
}
private fun parseFinancingHtml(html: String): List<MibFinanceDeal> {
val cardPattern = Regex("""finance-card-holder[^>]+>""")
val attrPattern = Regex("""data-(\w+)\s*=\s*"([^"]*)"""")
return cardPattern.findAll(html).mapNotNull { cardMatch ->
val attrs = attrPattern.findAll(cardMatch.value)
.associate { it.groupValues[1] to it.groupValues[2] }
val dealNo = attrs["dealNo"] ?: return@mapNotNull null
MibFinanceDeal(
dealNo = dealNo,
productDesc = attrs["productDesc"] ?: "",
dealStatus = attrs["dealStatus"] ?: "",
statusDesc = attrs["statusDesc"] ?: "",
dealAmount = attrs["dealAmount"]?.toDoubleOrNull() ?: 0.0,
paidAmount = attrs["paidAmount"]?.toDoubleOrNull() ?: 0.0,
outstandingAmount = attrs["outstandingAmount"]?.toDoubleOrNull() ?: 0.0,
dealDate = attrs["dealDate"] ?: "",
overdueAmount = attrs["overdueAmount"]?.toDoubleOrNull() ?: 0.0,
installmentAmount = attrs["installmentAmount"]?.toDoubleOrNull() ?: 0.0,
noOfInstallments = attrs["noOfInstallments"]?.toIntOrNull() ?: 0,
lastPaidDate = attrs["lastPaidDate"] ?: "",
lastPayAmount = attrs["lastPayAmount"]?.toDoubleOrNull() ?: 0.0,
currency = attrs["curCodeDesc"] ?: "MVR"
)
}.toList().also { Log.d(TAG, "parsed ${it.size} financing deals") }
}
companion object {
/** Estimate remaining months until financing is fully paid. */
fun remainingMonths(deal: MibFinanceDeal): Int {
if (deal.installmentAmount <= 0.0) return 0
return ceil(deal.outstandingAmount / deal.installmentAmount).toInt()
}
}
}
@@ -15,6 +15,14 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
private val TAG = "MibLoginFlow"
private val BASE_URL = "https://faisanet.mib.com.mv/faisamobilex_smvc/"
/** The active session after a successful login, usable for subsequent WebView requests. */
var lastSession: MibSession? = null
private set
/** Profiles returned by the last successful login. */
var lastProfiles: List<MibProfile> = emptyList()
private set
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
@@ -140,6 +148,8 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
val profiles = parseProfiles(loginResp)
Log.d(TAG, "[login] parsed ${profiles.size} profiles")
lastSession = session2
lastProfiles = profiles
Log.d(TAG, "[login] step 7: fetch all profiles")
return fetchAllProfiles(session2, profiles)
}
@@ -213,6 +223,19 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
put("xxid", session.xxid)
}
/**
* Activates [profile] server-side via P47, setting the session role context so that
* subsequent WebView requests run under that profile.
*/
fun switchProfile(session: MibSession, profile: MibProfile) {
Log.d(TAG, "switchProfile: profileId=${profile.profileId} cifType=${profile.cifType}")
val payload = baseData(session, "P47").apply {
put("profileType", profile.profileType)
put("profileId", profile.profileId)
}
doRequest(session, payload, "n")
}
private fun fetchAllProfiles(session: MibSession, profiles: List<MibProfile>): List<MibAccount> {
val allAccounts = mutableListOf<MibAccount>()
for (profile in profiles) {
@@ -31,3 +31,20 @@ data class MibAccount(
val mvrBalance: String,
val statusDesc: String
)
data class MibFinanceDeal(
val dealNo: String,
val productDesc: String,
val dealStatus: String,
val statusDesc: String,
val dealAmount: Double,
val paidAmount: Double,
val outstandingAmount: Double,
val dealDate: String,
val overdueAmount: Double,
val installmentAmount: Double,
val noOfInstallments: Int,
val lastPaidDate: String,
val lastPayAmount: Double,
val currency: String
)