forked from shihaam/thijooree
fix some currency detection issues with MIB to MIB, and also Option to automatically add contact during BML to MIB USD transfer #35 (its not fully auto)
This commit is contained in:
@@ -71,7 +71,8 @@ class BmlValidateClient {
|
|||||||
originalInput = account,
|
originalInput = account,
|
||||||
name = root.optString("name"),
|
name = root.optString("name"),
|
||||||
alias = null,
|
alias = null,
|
||||||
currency = "MVR",
|
// BML's MIB verify endpoint doesn't return the MIB account's currency.
|
||||||
|
currency = "",
|
||||||
agnt = root.optString("agnt").takeIf { it.isNotBlank() }
|
agnt = root.optString("agnt").takeIf { it.isNotBlank() }
|
||||||
)
|
)
|
||||||
} catch (_: Exception) { null }
|
} catch (_: Exception) { null }
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ data class MibTransferResult(
|
|||||||
data class MibIpsAccountInfo(
|
data class MibIpsAccountInfo(
|
||||||
val accountName: String,
|
val accountName: String,
|
||||||
val accountNumber: String,
|
val accountNumber: String,
|
||||||
val bankId: String
|
val bankId: String,
|
||||||
|
val currency: String = "" // "MVR", "USD", or "" if unknown
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ class MibTransferClient {
|
|||||||
MibIpsAccountInfo(
|
MibIpsAccountInfo(
|
||||||
accountName = json.optString("accountName").trim(),
|
accountName = json.optString("accountName").trim(),
|
||||||
accountNumber = accountNumber,
|
accountNumber = accountNumber,
|
||||||
bankId = json.optString("bankBic")
|
bankId = json.optString("bankBic"),
|
||||||
|
// MIB IPS only returns success for MVR cross-bank accounts;
|
||||||
|
// USD cross-bank accounts fail this lookup entirely.
|
||||||
|
currency = "MVR"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,10 +159,18 @@ class MibTransferClient {
|
|||||||
// accountName may be at root or inside a "data" object
|
// accountName may be at root or inside a "data" object
|
||||||
val name = json.optString("accountName").takeIf { it.isNotBlank() }
|
val name = json.optString("accountName").takeIf { it.isNotBlank() }
|
||||||
?: json.optJSONObject("data")?.optString("accountName") ?: ""
|
?: json.optJSONObject("data")?.optString("accountName") ?: ""
|
||||||
|
val currencyCode = json.optString("currencyCode").takeIf { it.isNotBlank() }
|
||||||
|
?: json.optJSONObject("data")?.optString("currencyCode") ?: ""
|
||||||
|
val currency = when (currencyCode) {
|
||||||
|
"840" -> "USD"
|
||||||
|
"462" -> "MVR"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
MibIpsAccountInfo(
|
MibIpsAccountInfo(
|
||||||
accountName = name.trim(),
|
accountName = name.trim(),
|
||||||
accountNumber = accountNumber,
|
accountNumber = accountNumber,
|
||||||
bankId = "MADVMVMV" // MIB
|
bankId = "MADVMVMV", // MIB
|
||||||
|
currency = currency
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,52 @@ class AddContactSheetFragment : BottomSheetDialogFragment() {
|
|||||||
categories = cats.filter { it.id != "BML" }
|
categories = cats.filter { it.id != "BML" }
|
||||||
if (selectedDest?.isBml == false) setupCategoryDropdown()
|
if (selectedDest?.isBml == false) setupCategoryDropdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyPrefillArgs()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyPrefillArgs() {
|
||||||
|
val args = arguments ?: return
|
||||||
|
val bmlProfileId = args.getString(ARG_BML_PROFILE_ID)
|
||||||
|
val accountNumber = args.getString(ARG_ACCOUNT_NUMBER)
|
||||||
|
val recipientName = args.getString(ARG_RECIPIENT_NAME)
|
||||||
|
val currency = args.getString(ARG_CURRENCY)
|
||||||
|
|
||||||
|
if (bmlProfileId != null) {
|
||||||
|
val match = destinations.firstOrNull { it.isBml && it.bmlLoginId == bmlProfileId }
|
||||||
|
if (match != null) {
|
||||||
|
selectedDest = match
|
||||||
|
binding.actvDestination.setText(match.label, false)
|
||||||
|
updateMibOnlyVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountNumber != null) {
|
||||||
|
binding.etAccount.setText(accountNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip lookup only when we have a MIB-verified name+currency from the caller.
|
||||||
|
if (selectedDest != null && accountNumber != null &&
|
||||||
|
!recipientName.isNullOrBlank() && !currency.isNullOrBlank()
|
||||||
|
) {
|
||||||
|
val bankBic = when {
|
||||||
|
accountNumber.matches(Regex("^9\\d{16}$")) -> "MADVMVMV"
|
||||||
|
accountNumber.matches(Regex("^7\\d{12}$")) -> "MALBMVMV"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
val trnType = if (accountNumber.matches(Regex("^9\\d{16}$"))) "DOT" else "IAT"
|
||||||
|
val validation = BmlAccountValidation(
|
||||||
|
trnType = trnType,
|
||||||
|
validationType = "prefilled",
|
||||||
|
account = accountNumber,
|
||||||
|
originalInput = accountNumber,
|
||||||
|
name = recipientName,
|
||||||
|
alias = null,
|
||||||
|
currency = currency,
|
||||||
|
agnt = bankBic.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
|
showLookupResult(validation, accountNumber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildDestinations(): List<DestinationOption> {
|
private fun buildDestinations(): List<DestinationOption> {
|
||||||
@@ -517,5 +563,24 @@ class AddContactSheetFragment : BottomSheetDialogFragment() {
|
|||||||
companion object {
|
companion object {
|
||||||
// BML's internal UUID for MIB bank — used as the "swift" field when saving DOT contacts
|
// BML's internal UUID for MIB bank — used as the "swift" field when saving DOT contacts
|
||||||
private const val MIB_SWIFT_ON_BML = "F4E79935-3E73-E611-80DD-00155D020F0A"
|
private const val MIB_SWIFT_ON_BML = "F4E79935-3E73-E611-80DD-00155D020F0A"
|
||||||
|
|
||||||
|
private const val ARG_BML_PROFILE_ID = "bml_profile_id"
|
||||||
|
private const val ARG_ACCOUNT_NUMBER = "account_number"
|
||||||
|
private const val ARG_RECIPIENT_NAME = "recipient_name"
|
||||||
|
private const val ARG_CURRENCY = "currency"
|
||||||
|
|
||||||
|
fun newInstance(
|
||||||
|
bmlProfileId: String? = null,
|
||||||
|
accountNumber: String? = null,
|
||||||
|
recipientName: String? = null,
|
||||||
|
currency: String? = null
|
||||||
|
) = AddContactSheetFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
if (bmlProfileId != null) putString(ARG_BML_PROFILE_ID, bmlProfileId)
|
||||||
|
if (accountNumber != null) putString(ARG_ACCOUNT_NUMBER, accountNumber)
|
||||||
|
if (recipientName != null) putString(ARG_RECIPIENT_NAME, recipientName)
|
||||||
|
if (currency != null) putString(ARG_CURRENCY, currency)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class TransferFragment : Fragment() {
|
|||||||
private var resolvedAccountNumber = ""
|
private var resolvedAccountNumber = ""
|
||||||
private var resolvedRecipientName = ""
|
private var resolvedRecipientName = ""
|
||||||
private var resolvedBankName = ""
|
private var resolvedBankName = ""
|
||||||
|
private var resolvedDestCurrency = "" // "MVR" / "USD" / "" if unknown
|
||||||
private var resolvedToOwnAccount: BankAccount? = null
|
private var resolvedToOwnAccount: BankAccount? = null
|
||||||
|
|
||||||
// Selected Fahipay service when source is Fahipay and destination is a phone number
|
// Selected Fahipay service when source is Fahipay and destination is a phone number
|
||||||
@@ -661,6 +662,7 @@ class TransferFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
resolvedAccountNumber = ""
|
resolvedAccountNumber = ""
|
||||||
resolvedRecipientName = ""
|
resolvedRecipientName = ""
|
||||||
|
resolvedDestCurrency = ""
|
||||||
resolvedToOwnAccount = null
|
resolvedToOwnAccount = null
|
||||||
selectedFahipayService = null
|
selectedFahipayService = null
|
||||||
binding.cardToInfo.visibility = View.GONE
|
binding.cardToInfo.visibility = View.GONE
|
||||||
@@ -677,6 +679,7 @@ class TransferFragment : Fragment() {
|
|||||||
if (binding.cardToInfo.visibility == View.VISIBLE) {
|
if (binding.cardToInfo.visibility == View.VISIBLE) {
|
||||||
resolvedAccountNumber = ""
|
resolvedAccountNumber = ""
|
||||||
resolvedRecipientName = ""
|
resolvedRecipientName = ""
|
||||||
|
resolvedDestCurrency = ""
|
||||||
resolvedToOwnAccount = null
|
resolvedToOwnAccount = null
|
||||||
binding.cardToInfo.visibility = View.GONE
|
binding.cardToInfo.visibility = View.GONE
|
||||||
binding.tilTo.visibility = View.VISIBLE
|
binding.tilTo.visibility = View.VISIBLE
|
||||||
@@ -760,7 +763,16 @@ class TransferFragment : Fragment() {
|
|||||||
"IAT" -> "MALBMVMV"
|
"IAT" -> "MALBMVMV"
|
||||||
else -> bmlResult.agnt ?: bmlResult.account
|
else -> bmlResult.agnt ?: bmlResult.account
|
||||||
}
|
}
|
||||||
MibIpsAccountInfo(accountName = bmlResult.name, accountNumber = bmlResult.account, bankId = bankId)
|
// BML's MIB verify endpoint doesn't return the account's currency.
|
||||||
|
// Enrich via MIB lookup when a MIB session is available.
|
||||||
|
val currency = if (
|
||||||
|
inputType == AccountInputParser.InputType.MIB_ACCOUNT &&
|
||||||
|
bmlResult.currency.isBlank() && mibSess != null
|
||||||
|
) {
|
||||||
|
try { MibTransferClient().lookup(mibSess, bmlResult.account).currency }
|
||||||
|
catch (_: Exception) { "" }
|
||||||
|
} else bmlResult.currency
|
||||||
|
MibIpsAccountInfo(accountName = bmlResult.name, accountNumber = bmlResult.account, bankId = bankId, currency = currency)
|
||||||
} else if (mibSess != null) {
|
} else if (mibSess != null) {
|
||||||
try { MibTransferClient().lookup(mibSess, accountNumber) }
|
try { MibTransferClient().lookup(mibSess, accountNumber) }
|
||||||
catch (e: MibLookupException) { errorMsg = e.message; null }
|
catch (e: MibLookupException) { errorMsg = e.message; null }
|
||||||
@@ -769,21 +781,29 @@ class TransferFragment : Fragment() {
|
|||||||
errorMsg = getString(R.string.transfer_account_not_found); null
|
errorMsg = getString(R.string.transfer_account_not_found); null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mibSess != null) {
|
val mibInfo = if (mibSess != null) {
|
||||||
try { MibTransferClient().lookup(mibSess, accountNumber) }
|
try { MibTransferClient().lookup(mibSess, accountNumber) }
|
||||||
catch (e: MibLookupException) { errorMsg = e.message; null }
|
catch (e: MibLookupException) { errorMsg = e.message; null }
|
||||||
catch (_: Exception) { errorMsg = getString(R.string.transfer_account_not_found); null }
|
catch (_: Exception) { errorMsg = getString(R.string.transfer_account_not_found); null }
|
||||||
} else {
|
} else null
|
||||||
val bmlResult = try { BmlValidateClient().validateAccount(bmlSess!!, accountNumber) } catch (_: Exception) { null }
|
if (mibInfo != null) {
|
||||||
|
mibInfo
|
||||||
|
} else if (bmlSess != null) {
|
||||||
|
val bmlResult = try { BmlValidateClient().validateAccount(bmlSess, accountNumber) } catch (_: Exception) { null }
|
||||||
if (bmlResult != null) {
|
if (bmlResult != null) {
|
||||||
|
errorMsg = null
|
||||||
val bankId = when (bmlResult.trnType) {
|
val bankId = when (bmlResult.trnType) {
|
||||||
"IAT" -> "MALBMVMV"
|
"IAT" -> "MALBMVMV"
|
||||||
else -> bmlResult.agnt ?: bmlResult.account
|
else -> bmlResult.agnt ?: bmlResult.account
|
||||||
}
|
}
|
||||||
MibIpsAccountInfo(accountName = bmlResult.name, accountNumber = bmlResult.account, bankId = bankId)
|
MibIpsAccountInfo(accountName = bmlResult.name, accountNumber = bmlResult.account, bankId = bankId, currency = bmlResult.currency)
|
||||||
} else {
|
} else {
|
||||||
errorMsg = getString(R.string.transfer_account_not_found); null
|
if (errorMsg == null) errorMsg = getString(R.string.transfer_account_not_found)
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (errorMsg == null) errorMsg = getString(R.string.transfer_account_not_found)
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -799,6 +819,7 @@ class TransferFragment : Fragment() {
|
|||||||
resolvedAccountNumber = info.accountNumber
|
resolvedAccountNumber = info.accountNumber
|
||||||
resolvedRecipientName = info.accountName
|
resolvedRecipientName = info.accountName
|
||||||
resolvedBankName = info.bankId
|
resolvedBankName = info.bankId
|
||||||
|
resolvedDestCurrency = info.currency
|
||||||
savedToSubtitle = "${info.accountNumber} · ${info.bankId}"
|
savedToSubtitle = "${info.accountNumber} · ${info.bankId}"
|
||||||
savedToColorHex = colorHex
|
savedToColorHex = colorHex
|
||||||
savedToImageHash = when {
|
savedToImageHash = when {
|
||||||
@@ -979,6 +1000,7 @@ class TransferFragment : Fragment() {
|
|||||||
private fun prefillToFromContact(accountNumber: String, label: String) {
|
private fun prefillToFromContact(accountNumber: String, label: String) {
|
||||||
resolvedAccountNumber = ""
|
resolvedAccountNumber = ""
|
||||||
resolvedRecipientName = ""
|
resolvedRecipientName = ""
|
||||||
|
resolvedDestCurrency = ""
|
||||||
binding.cardToInfo.visibility = View.GONE
|
binding.cardToInfo.visibility = View.GONE
|
||||||
binding.tilTo.visibility = View.VISIBLE
|
binding.tilTo.visibility = View.VISIBLE
|
||||||
binding.btnPickContact.visibility = View.VISIBLE
|
binding.btnPickContact.visibility = View.VISIBLE
|
||||||
@@ -1115,10 +1137,27 @@ class TransferFragment : Fragment() {
|
|||||||
if (isSrcBml && isDestMib && currency == "USD") {
|
if (isSrcBml && isDestMib && currency == "USD") {
|
||||||
val hasBmlContact = allContacts.any { it.benefCategoryId == "BML" && it.benefAccount == resolvedAccountNumber }
|
val hasBmlContact = allContacts.any { it.benefCategoryId == "BML" && it.benefAccount == resolvedAccountNumber }
|
||||||
if (!hasBmlContact) {
|
if (!hasBmlContact) {
|
||||||
|
// If we verified the dest currency via MIB fallback, the block is purely a BML API limitation.
|
||||||
|
// Otherwise (no MIB session, currency truly unknown) the generic message applies.
|
||||||
|
val msgRes = if (resolvedDestCurrency.isNotBlank())
|
||||||
|
R.string.transfer_bml_contact_required_msg_bml_limit
|
||||||
|
else
|
||||||
|
R.string.transfer_bml_contact_required_msg
|
||||||
|
val bmlProfileId = src.profileId.takeIf { it.isNotBlank() }
|
||||||
|
?: src.loginTag.removePrefix("bml_").takeIf { it.isNotBlank() }
|
||||||
|
val verified = resolvedDestCurrency.isNotBlank()
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.transfer_bml_contact_required_title)
|
.setTitle(R.string.transfer_bml_contact_required_title)
|
||||||
.setMessage(R.string.transfer_bml_contact_required_msg)
|
.setMessage(msgRes)
|
||||||
.setPositiveButton(R.string.close, null)
|
.setPositiveButton(R.string.contact_save) { _, _ ->
|
||||||
|
AddContactSheetFragment.newInstance(
|
||||||
|
bmlProfileId = bmlProfileId,
|
||||||
|
accountNumber = resolvedAccountNumber,
|
||||||
|
recipientName = if (verified) resolvedRecipientName else null,
|
||||||
|
currency = if (verified) resolvedDestCurrency else null
|
||||||
|
).show(parentFragmentManager, "add_contact")
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.close, null)
|
||||||
.show()
|
.show()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1128,11 +1167,13 @@ class TransferFragment : Fragment() {
|
|||||||
val bankNameCapture = resolvedBankName
|
val bankNameCapture = resolvedBankName
|
||||||
val capturedToAvatar = (binding.ivToPhoto.drawable as? android.graphics.drawable.BitmapDrawable)?.bitmap
|
val capturedToAvatar = (binding.ivToPhoto.drawable as? android.graphics.drawable.BitmapDrawable)?.bitmap
|
||||||
|
|
||||||
val destCurrency = allAccounts.firstOrNull { it.accountNumber == resolvedAccountNumber }
|
val destCurrency = resolvedDestCurrency.ifBlank {
|
||||||
?.currencyName?.ifBlank { "MVR" }
|
allAccounts.firstOrNull { it.accountNumber == resolvedAccountNumber }
|
||||||
?: allContacts.firstOrNull { it.benefAccount == resolvedAccountNumber }
|
?.currencyName?.ifBlank { "MVR" }
|
||||||
?.transferCyDesc?.ifBlank { "MVR" }
|
?: allContacts.firstOrNull { it.benefAccount == resolvedAccountNumber }
|
||||||
?: if (isDestMib) "MVR" else "MVR"
|
?.transferCyDesc?.ifBlank { "MVR" }
|
||||||
|
?: "MVR"
|
||||||
|
}
|
||||||
val isUsdToMvr = currency.equals("USD", ignoreCase = true) && destCurrency.equals("MVR", ignoreCase = true)
|
val isUsdToMvr = currency.equals("USD", ignoreCase = true) && destCurrency.equals("MVR", ignoreCase = true)
|
||||||
val isSrcCredit = src.profileType == "BML_CREDIT"
|
val isSrcCredit = src.profileType == "BML_CREDIT"
|
||||||
|
|
||||||
@@ -2030,6 +2071,7 @@ class TransferFragment : Fragment() {
|
|||||||
resolvedAccountNumber = ""
|
resolvedAccountNumber = ""
|
||||||
resolvedRecipientName = ""
|
resolvedRecipientName = ""
|
||||||
resolvedBankName = ""
|
resolvedBankName = ""
|
||||||
|
resolvedDestCurrency = ""
|
||||||
resolvedToOwnAccount = null
|
resolvedToOwnAccount = null
|
||||||
selectedFahipayService = null
|
selectedFahipayService = null
|
||||||
binding.cardToInfo.visibility = View.GONE
|
binding.cardToInfo.visibility = View.GONE
|
||||||
|
|||||||
@@ -280,7 +280,8 @@
|
|||||||
<string name="transfer_confirm">Confirm</string>
|
<string name="transfer_confirm">Confirm</string>
|
||||||
<string name="transfer_success">Transfer Successful</string>
|
<string name="transfer_success">Transfer Successful</string>
|
||||||
<string name="transfer_bml_contact_required_title">Contact Required</string>
|
<string name="transfer_bml_contact_required_title">Contact Required</string>
|
||||||
<string name="transfer_bml_contact_required_msg">To send USD to a MIB account from BML, the recipient must be saved as a BML contact first. This is required by BML\'s API.\n\nPlease add this account as a BML contact, then try again.</string>
|
<string name="transfer_bml_contact_required_msg">We couldn\'t verify the recipient\'s currency for this transfer.\n\nPlease save them as a contact, manually select the correct currency, then try again.</string>
|
||||||
|
<string name="transfer_bml_contact_required_msg_bml_limit">BML\'s API requires the recipient to be saved as a contact when sending USD to a non-BML account, even though we verified the account.\n\nPlease save them as a contact, then try again.</string>
|
||||||
<string name="transfer_missing_internal_id">Account data is incomplete — please re-login to refresh.</string>
|
<string name="transfer_missing_internal_id">Account data is incomplete — please re-login to refresh.</string>
|
||||||
<string name="transfer_verify_payment">Verify Payment</string>
|
<string name="transfer_verify_payment">Verify Payment</string>
|
||||||
<string name="transfer_send_otp_via">Send verification code via</string>
|
<string name="transfer_send_otp_via">Send verification code via</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user