taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

commit 0c817c3ab94cd068af289c1f9f21718c4c23b30d
parent 993a2b5880d2ce79e5c12fe049afb69234120755
Author: Marc Stibane <marc@taler.net>
Date:   Fri, 14 Aug 2026 22:00:24 +0200

fixes for currency conversion (e.g. NetzBon)

Diffstat:
MTalerWallet1/Views/Transactions/KYCauth.swift | 30++++++++++++++++++------------
MTalerWallet1/Views/Transactions/ManualDetailsV.swift | 52++++++++++++++++++++++++++++++----------------------
MTalerWallet1/Views/Transactions/ManualDetailsWireV.swift | 21++++++++++++++++-----
3 files changed, 64 insertions(+), 39 deletions(-)

diff --git a/TalerWallet1/Views/Transactions/KYCauth.swift b/TalerWallet1/Views/Transactions/KYCauth.swift @@ -9,25 +9,31 @@ import SwiftUI import SymLog import taler_swift +struct KycData { + let kycAmount: Amount + let debitIBAN: String +} // pretty much the same as ManualDetailsV struct KYCauth: View { let stack: CallStack let common: TransactionCommon var body: some View { + let kycErr = "KYC Error" if let info = common.kycAuthTransferInfo { let debitPayTo = PayTo(info.debitPaytoUri) - let accountDetails = info.transferOptionsExt - if let validDetails = accountDetails.validDetails { - WireTransferView(stack: stack.push(), - title: String(localized: "You need to prove having control over the bank account for the deposit.", comment: ""), - isKYC: true, - reservePub: info.accountPub, - amountRaw: info.amount, - amountEffective: nil, - debitIBAN: debitPayTo.iban, - validDetails: validDetails) - } else { ErrorView(stack.push(), title: "KYC Error", message: "No valid details") } - } else { ErrorView(stack.push(), title: "KYC Error", message: "No KYC infos") } + if let debitIban = debitPayTo.iban { + let kycData = KycData(kycAmount: info.amount, debitIBAN: debitIban) + let accountDetails = info.transferOptionsExt + if let validDetails = accountDetails.validDetails { + WireTransferView(stack: stack.push(), + title: String(localized: "You need to prove having control over the bank account for the deposit.", comment: ""), + reservePub: info.accountPub, + obtainStr: nil, + kycData: kycData, + validDetails: validDetails) + } else { ErrorView(stack.push(), title: kycErr, message: "No valid details") } + } else { ErrorView(stack.push(), title: kycErr, message: "No IBAN") } + } else { ErrorView(stack.push(), title: kycErr, message: "No KYC infos") } } // body } diff --git a/TalerWallet1/Views/Transactions/ManualDetailsV.swift b/TalerWallet1/Views/Transactions/ManualDetailsV.swift @@ -15,27 +15,29 @@ struct ManualDetailsV: View { let details: WithdrawalDetails var body: some View { - if let validDetails = details.exchangeCreditAccountDetails?.validDetails { - WireTransferView(stack: stack.push(), - title: String(localized: "The payment service is waiting for your wire-transfer.", comment: ""), - isKYC: false, - reservePub: details.reservePub, - amountRaw: common.amountRaw, - amountEffective: common.amountEffective, // only for withdrawal - debitIBAN: nil, // only for deposit auth - validDetails: validDetails) + if let accountDetails = details.exchangeCreditAccountDetails, + let validDetails = accountDetails.validDetails { + if let scope: ScopeInfo = common.scopes.first { + let obtainStr = common.amountEffective.formatted(scope , isNegative: false) + WireTransferView(stack: stack.push(), + title: String(localized: "The payment service is waiting for your wire-transfer.", comment: ""), + reservePub: details.reservePub, + obtainStr: obtainStr, // only for withdrawal + kycData: nil, // only for deposit auth + validDetails: validDetails) + } else { ErrorView(stack.push(), title: "Payment Error", message: "No scope") } } else { ErrorView(stack.push(), title: "Payment Error", message: "No valid details") } } // body } // MARK: - +// called by ManualDetailsV (amount to transfer is chosen by user), +// and by KYCauth (user chose the amount to deposit - but first they must transfer kycAmount to the Exchange to prove having control over the account) struct WireTransferView: View { let stack: CallStack let title: String - let isKYC: Bool let reservePub: String - let amountRaw: Amount - let amountEffective: Amount? // only for withdrawal - let debitIBAN: String? // only for deposit auth + let obtainStr: (String, String)? // only for withdrawal + let kycData: KycData? // only for deposit auth let validDetails: AccountDetailsArray @EnvironmentObject private var model: WalletModel @@ -67,14 +69,20 @@ struct WireTransferView: View { var body: some View { let validCount = validDetails.count // guaranteed to be > 0 - let account = validDetails[validIndex] + // validDetails can shrink (e.g. when a conversion rate lookup starts failing) + // while validIndex still points to an account which is gone by now + let account = validDetails[min(validIndex, validCount - 1)] let json = account.toJSON() - if account.transferAmount != nil { - if let transferOptions = account.transferOptions, let firstOption = transferOptions.first { + // transferAmount is the amount the user must wire - for an account which needs + // currency conversion that is in the external currency, and thus different from amountRaw + if kycData != nil || account.transferAmount != nil { + if let transferOptions = account.transferOptions, + // an option of type "uri" has neither paytoUri nor qrCodes - use one which has + let firstOption = transferOptions.first(where: { $0.paytoUri != nil }) ?? transferOptions.first { let specs = account.currencySpecification - let amountStr = amountRaw.formatted(specs: specs, isNegative: false) - let amountValue = amountRaw.valueStr - let obtainStr = amountEffective?.formatted(specs: specs, isNegative: false) // or nil + let plannedAmount = kycData?.kycAmount ?? account.transferAmount! + let amountStr = plannedAmount.formatted(specs: specs, isNegative: false) + let amountValue = plannedAmount.valueStr // let _ = print(amountStr, " | ", obtainStr) if !minimalistic { Text(title) @@ -114,8 +122,8 @@ struct WireTransferView: View { restrictions: account.creditRestrictions, amountValue: amountValue, amountStr: amountStr, - obtainStr: obtainStr, // only for withdrawal - debitIBAN: debitIBAN // only for deposit auth + obtainStr: obtainStr, // only for withdrawal + debitIBAN: kycData?.debitIBAN // only for deposit auth ) NavigationLink(destination: wireDetails) { Text(minimalistic ? "Instructions" @@ -126,7 +134,7 @@ struct WireTransferView: View { Group { if countA > 1 || countB > 1 { qrCodesRow(transferOptions) - } else if countA > 0 { // show the single QR directly + } else if countB > 0 { // show the single QR directly Text("If your banking software runs on another device, you can scan this QR code:") .listRowSeparator(.hidden) QRcodeCopyShare(option: firstOption) diff --git a/TalerWallet1/Views/Transactions/ManualDetailsWireV.swift b/TalerWallet1/Views/Transactions/ManualDetailsWireV.swift @@ -11,8 +11,8 @@ import taler_swift struct TransferRestrictionsV: View { let amountStr: (String, String) - let obtainStr: (String, String)? - let debitIBAN: String? + let obtainStr: (String, String)? // only for withdrawal + let debitIBAN: String? // only for deposit auth let restrictions: [AccountRestriction]? @AppStorage("minimalistic") var minimalistic: Bool = false @@ -38,14 +38,14 @@ struct TransferRestrictionsV: View { var body: some View { VStack(alignment: .leading) { - if let obtainStr { + if let obtainStr { // withdrawal Text(minimalistic ? transferMini(amountStr.0) : transferMaxi(amountStr.0, obtainStr.0)) .accessibilityLabel(minimalistic ? transferMini(amountStr.1) : transferMaxi(amountStr.1, obtainStr.1)) .talerFont(.body) .multilineTextAlignment(.leading) - } else if let debitIBAN { + } else if let debitIBAN { // deposit auth Text(minimalistic ? authMini(amountStr.0, debitIBAN) : authMaxi(amountStr.0, debitIBAN)) .accessibilityLabel(minimalistic ? authMini(amountStr.1, debitIBAN) @@ -280,6 +280,18 @@ struct ManualDetailsWireV: View { : String(localized: "Finish the wire transfer of \(amountNBS) in your banking app or website, then this withdrawal will proceed automatically.") + "\n" + bePatient } + /// The subject of the wire transfer + private var cryptoString: String { + // chQRr only consists of digits - no prefix possible + if let chQRr = payto.chQRr, !chQRr.isEmpty { + return chQRr + } +// if let messageStr = payto.messageStr, !messageStr.isEmpty { +// return messageStr +// } + return debitIBAN != nil ? "KYC:" + reservePub : reservePub + } + // @ViewBuilder func cyclosCode() -> some View { // HStack { // VStack(alignment: .leading) { @@ -309,7 +321,6 @@ struct ManualDetailsWireV: View { var body: some View { if let receiverStr = payto.receiver { List { - let cryptoString = debitIBAN == nil ? reservePub : "KYC:" + reservePub let warningIcon = Image(systemName: WARNING) let note = Text("**Note: Don't forget to copy and paste the code in Step 2.**") let manda = debitIBAN == nil ? String(localized: "This is mandatory, otherwise your money will not arrive in this wallet.")