commit 08fe4a1daecf5b7357f8c396aa5e871f7444c464
parent 596b8fa07cb542695f4e87076faa4670cf55ce10
Author: Iván Ávalos <avalos@disroot.org>
Date: Mon, 21 Jul 2025 19:01:05 +0200
[wallet] render postal code and town in transfer details
bug 0010182
Diffstat:
11 files changed, 105 insertions(+), 45 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/accounts/Accounts.kt b/wallet/src/main/java/net/taler/wallet/accounts/Accounts.kt
@@ -23,6 +23,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonClassDiscriminator
import net.taler.common.Bech32
import net.taler.wallet.backend.TalerErrorInfo
+import androidx.core.net.toUri
@Serializable
data class KnownBankAccountInfo(
@@ -80,7 +81,7 @@ sealed class PaytoUri(
companion object {
fun parse(paytoUri: String): PaytoUri? {
- val uri = Uri.parse(paytoUri)
+ val uri = paytoUri.toUri()
if (uri.scheme != "payto") return null
if (uri.pathSegments.isEmpty()) return null
return when (uri.authority?.lowercase()) {
@@ -101,6 +102,8 @@ data class PaytoUriIban(
override val targetPath: String,
override val params: Map<String, String>,
override val receiverName: String?,
+ val receiverPostalCode: String?,
+ val receiverTown: String?,
) : PaytoUri(
isKnown = true,
targetType = "iban",
@@ -111,12 +114,16 @@ data class PaytoUriIban(
.authority(targetType)
.apply { if (bic != null) appendPath(bic) }
.appendPath(iban)
+ .appendQueryParameter("receiver-name", receiverName)
+ .appendQueryParameter("receiver-postal-code", receiverPostalCode)
+ .appendQueryParameter("receiver-town", receiverTown)
.apply {
params.forEach { (key, value) ->
- appendQueryParameter(key, value)
+ if (value.isNotEmpty() && build().getQueryParameter(key) == null) {
+ appendQueryParameter(key, value)
+ }
}
- }
- .build().toString()
+ }.build().toString()
companion object {
fun fromString(uri: Uri): PaytoUriIban? {
@@ -127,6 +134,8 @@ data class PaytoUriIban(
} else null,
params = uri.queryParametersMap,
receiverName = uri.getQueryParameter("receiver-name"),
+ receiverPostalCode = uri.getQueryParameter("receiver-postal-code"),
+ receiverTown = uri.getQueryParameter("receiver-town"),
targetPath = "",
)
}
@@ -153,7 +162,9 @@ data class PaytoUriTalerBank(
.appendPath(account)
.apply {
params.forEach { (key, value) ->
- appendQueryParameter(key, value)
+ if (value.isNotEmpty()) {
+ appendQueryParameter(key, value)
+ }
}
}
.build().toString()
@@ -194,7 +205,9 @@ data class PaytoUriBitcoin(
}
.apply {
params.forEach { (key, value) ->
- appendQueryParameter(key, value)
+ if (value.isNotEmpty()) {
+ appendQueryParameter(key, value)
+ }
}
}
.build().toString()
diff --git a/wallet/src/main/java/net/taler/wallet/accounts/AccountsFragment.kt b/wallet/src/main/java/net/taler/wallet/accounts/AccountsFragment.kt
@@ -290,6 +290,8 @@ val previewKnownAccounts = listOf(
targetPath = "",
params = emptyMap(),
receiverName = "John Doe",
+ receiverPostalCode = "1234",
+ receiverTown = "Texas",
).paytoUri,
kycCompleted = true,
currencies = listOf("KUDOS"),
diff --git a/wallet/src/main/java/net/taler/wallet/accounts/AddAccountComposable.kt b/wallet/src/main/java/net/taler/wallet/accounts/AddAccountComposable.kt
@@ -101,6 +101,8 @@ fun AddAccountComposable(
var ibanError by rememberSaveable(presetPaytoUri) { mutableStateOf(presetPaytoUri == null) }
var formAlias by rememberSaveable(presetAccount) { mutableStateOf(presetAccount?.label ?: "") }
var ibanName by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.receiverName ?: "") }
+ var ibanTown by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.receiverTown) }
+ var ibanZip by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.receiverPostalCode) }
var ibanIban by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.iban ?: "") }
var talerName by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriTalerBank)?.receiverName ?: "") }
var talerHost by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriTalerBank)?.host ?: talerBankHostnames.firstOrNull() ?: "") }
@@ -108,7 +110,7 @@ fun AddAccountComposable(
var bitcoinAddress by rememberSaveable(presetPaytoUri) { mutableStateOf("") } // TODO: fill-in bitcoin address
val paytoUri = when(selectedWireType) {
- WireType.IBAN -> getIbanPayto(ibanName, ibanIban)
+ WireType.IBAN -> getIbanPayto(ibanName, ibanZip, ibanTown, ibanIban)
WireType.TalerBank -> getTalerPayto(talerName, talerHost, talerAccount)
WireType.Bitcoin -> getBitcoinPayto(bitcoinAddress)
else -> null
@@ -169,10 +171,14 @@ fun AddAccountComposable(
WireType.IBAN -> item {
AddAccountIBAN(
name = ibanName,
+ town = ibanTown,
+ zip = ibanZip,
iban = ibanIban,
ibanError = ibanError,
- onFormEdited = { name, iban ->
+ onFormEdited = { name, town, zip, iban ->
ibanName = name
+ ibanTown = town
+ ibanZip = zip
ibanIban = iban
}
)
diff --git a/wallet/src/main/java/net/taler/wallet/accounts/AddAccountIBAN.kt b/wallet/src/main/java/net/taler/wallet/accounts/AddAccountIBAN.kt
@@ -36,9 +36,11 @@ import net.taler.wallet.R
@Composable
fun AddAccountIBAN(
name: String,
+ town: String?,
+ zip: String?,
iban: String,
ibanError: Boolean,
- onFormEdited: (name: String, iban: String) -> Unit
+ onFormEdited: (name: String, town: String?, zip: String?, iban: String) -> Unit
) {
val focusManager = LocalFocusManager.current
OutlinedTextField(
@@ -50,7 +52,7 @@ fun AddAccountIBAN(
).fillMaxWidth(),
value = name,
onValueChange = { input ->
- onFormEdited(input, iban)
+ onFormEdited(input, town, zip, iban)
},
singleLine = true,
isError = name.isBlank(),
@@ -73,7 +75,7 @@ fun AddAccountIBAN(
value = iban,
singleLine = true,
onValueChange = { input ->
- onFormEdited(name, input
+ onFormEdited(name, town, zip, input
.uppercase()
.replace(" ", "")
.replace("\n", "")
@@ -101,4 +103,44 @@ fun AddAccountIBAN(
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }),
)
+
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(
+ bottom = 16.dp,
+ start = 16.dp,
+ end = 16.dp,
+ ).fillMaxWidth(),
+ value = zip ?: "",
+ singleLine = true,
+ onValueChange = { input ->
+ onFormEdited(name, town, input.trim(), iban)
+ },
+ isError = ibanError,
+ label = {
+ Text(stringResource(R.string.send_deposit_postal_code))
+ },
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }),
+ )
+
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(
+ bottom = 16.dp,
+ start = 16.dp,
+ end = 16.dp,
+ ).fillMaxWidth(),
+ value = town ?: "",
+ singleLine = true,
+ onValueChange = { input ->
+ onFormEdited(name, input.trim(), zip, iban)
+ },
+ isError = ibanError,
+ label = {
+ Text(stringResource(R.string.send_deposit_town))
+ },
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }),
+ )
}
\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt
@@ -54,10 +54,12 @@ class DepositFragment : Fragment() {
): View {
val presetAmount = arguments?.getString("amount")?.let { Amount.fromJSONString(it) }
val receiverName = arguments?.getString("receiverName")
+ val receiverPostalCode = arguments?.getString("receiverPostalCode")
+ val receiverTown = arguments?.getString("receiverTown")
val iban = arguments?.getString("IBAN")
if (presetAmount != null && receiverName != null && iban != null) {
- val paytoUri = getIbanPayto(receiverName, iban)
+ val paytoUri = getIbanPayto(receiverName, receiverPostalCode, receiverTown, iban)
depositManager.makeDeposit(presetAmount, paytoUri)
}
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt
@@ -185,12 +185,19 @@ class DepositManager(
}
}
-fun getIbanPayto(receiverName: String, iban: String) = PaytoUriIban(
+fun getIbanPayto(
+ receiverName: String,
+ receiverPostalCode: String?,
+ receiverTown: String?,
+ iban: String,
+) = PaytoUriIban(
iban = iban,
bic = null,
targetPath = "",
params = mapOf("receiver-name" to receiverName),
receiverName = receiverName,
+ receiverPostalCode = receiverPostalCode,
+ receiverTown = receiverTown,
).paytoUri
fun getTalerPayto(receiverName: String, host: String, account: String) = PaytoUriTalerBank(
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -311,6 +311,8 @@ data class WithdrawalExchangeAccountDetails (
TransferData.IBAN(
iban = uri.lastPathSegment!!,
receiverName = uri.getQueryParameter("receiver-name"),
+ receiverTown = uri.getQueryParameter("receiver-town"),
+ receiverPostalCode = uri.getQueryParameter("receiver-postal-code"),
subject = uri.getQueryParameter("message") ?: "Error: No message in URI",
amountRaw = amountRaw,
amountEffective = amountEffective,
diff --git a/wallet/src/main/java/net/taler/wallet/transfer/ScreenTransfer.kt b/wallet/src/main/java/net/taler/wallet/transfer/ScreenTransfer.kt
@@ -362,6 +362,8 @@ fun ScreenTransferPreview(
amountRaw = Amount("KUDOS", 10, 0),
amountEffective = Amount("KUDOS", 9, 5),
transferAmount = Amount("KUDOS", 10, 0),
+ receiverTown = "Biel/Bienne",
+ receiverPostalCode = "2500",
withdrawalAccount = WithdrawalExchangeAccountDetails(
paytoUri = "https://taler.net/kudos",
transferAmount = Amount("KUDOS", 10, 0),
diff --git a/wallet/src/main/java/net/taler/wallet/transfer/TransferIBAN.kt b/wallet/src/main/java/net/taler/wallet/transfer/TransferIBAN.kt
@@ -106,6 +106,14 @@ fun TransferIBAN(
DetailRow(stringResource(R.string.withdraw_manual_ready_receiver), it)
}
+ transfer.receiverPostalCode?.let {
+ DetailRow(stringResource(R.string.withdraw_manual_ready_postal_code), it)
+ }
+
+ transfer.receiverTown?.let {
+ DetailRow(stringResource(R.string.withdraw_manual_ready_town), it)
+ }
+
DetailRow(stringResource(R.string.withdraw_manual_ready_iban), transfer.iban)
WithdrawalAmountTransfer(
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -116,6 +116,8 @@ sealed class TransferData {
override val transferAmount: Amount,
override val withdrawalAccount: WithdrawalExchangeAccountDetails,
val receiverName: String? = null,
+ val receiverPostalCode: String? = null,
+ val receiverTown: String? = null,
val iban: String,
): TransferData()
@@ -514,38 +516,6 @@ class WithdrawManager(
}
}
- /**
- * A hack to be able to view bank details for manual withdrawal with the same logic.
- * Don't call this from ongoing withdrawal processes as it destroys state.
- */
- fun viewManualWithdrawal(
- transactionId: String,
- exchangeBaseUrl: String? = null,
- amountRaw: Amount,
- amountEffective: Amount,
- withdrawalAccountList: List<WithdrawalExchangeAccountDetails>,
- scopeInfo: ScopeInfo,
- ) {
- _withdrawStatus.value = createManualTransfer(
- status = WithdrawStatus(
- transactionId = transactionId,
- exchangeBaseUrl = exchangeBaseUrl,
- amountInfo = WithdrawalDetailsForAmount(
- amountRaw = amountRaw,
- amountEffective = amountEffective,
- withdrawalAccountsList = withdrawalAccountList,
- scopeInfo = scopeInfo,
- tosAccepted = true,
- )
- ),
- response = AcceptManualWithdrawalResponse(
- transactionId = transactionId,
- reservePub = "",
- withdrawalAccountsList = withdrawalAccountList,
- )
- )
- }
-
private fun createManualTransfer(
status: WithdrawStatus,
response: AcceptManualWithdrawalResponse,
@@ -590,6 +560,8 @@ class WithdrawManager(
TransferData.IBAN(
iban = uri.lastPathSegment!!,
receiverName = uri.getQueryParameter("receiver-name"),
+ receiverTown = uri.getQueryParameter("receiver-town"),
+ receiverPostalCode = uri.getQueryParameter("receiver-postal-code"),
subject = uri.getQueryParameter("message") ?: "Error: No message in URI",
amountRaw = details.amountRaw,
amountEffective = details.amountEffective,
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
@@ -268,9 +268,11 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="send_deposit_kyc_auth_warning_subject">This is mandatory, otherwise the verification will fail.</string>
<string name="send_deposit_name">Account holder</string>
<string name="send_deposit_no_methods_error">No supported wire methods</string>
+ <string name="send_deposit_postal_code">Postal code (optional)</string>
<string name="send_deposit_select_account_title">Select bank account</string>
<string name="send_deposit_select_amount_title">Select amount to deposit</string>
<string name="send_deposit_taler">x-taler-bank</string>
+ <string name="send_deposit_town">Town (optional)</string>
<string name="send_deposit_title">Deposit to bank account</string>
<string name="send_deposit_no_alias">No alias</string>
<string name="send_peer_create_button">Send funds now</string>
@@ -312,8 +314,10 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="withdraw_manual_ready_details_qr">Wire transfer QR codes</string>
<string name="withdraw_manual_ready_iban">IBAN</string>
<string name="withdraw_manual_ready_intro">You need to transfer %1$s from your regular bank account to the payment service to receive %2$s as electronic cash in this wallet.</string>
+ <string name="withdraw_manual_ready_postal_code">Postal code</string>
<string name="withdraw_manual_ready_receiver">Recipient</string>
<string name="withdraw_manual_ready_subject">Subject</string>
+ <string name="withdraw_manual_ready_town">Town</string>
<string name="withdraw_manual_ready_warning">This is mandatory, otherwise your money will not arrive in this wallet.</string>
<string name="withdraw_manual_step"><b>Step %1$s:</b> %2$s</string>
<string name="withdraw_manual_step_finish">Finish the wire transfer of %1$s in your banking app or website, then this withdrawal will proceed automatically. Depending on your bank the transfer can take from minutes to two working days, please be patient.</string>