taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit 299211675baba02d2f224c1783c13e3bece18549
parent 53d7a0b60b371d7bb2f6d7af425e436b78e073e8
Author: Iván Ávalos <avalos@disroot.org>
Date:   Tue, 15 Apr 2025 17:31:11 +0200

[wallet] check B-I-W status after scanning URI

bug 0009683

Diffstat:
Mwallet/src/main/java/net/taler/wallet/MainViewModel.kt | 2+-
Mwallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt | 9+++++----
Mwallet/src/main/java/net/taler/wallet/transactions/Transactions.kt | 2+-
Mwallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt | 40+++++++++++++++++++---------------------
Mwallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt | 10++++++----
Mwallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt | 60+++++++++++++++++++++++++++++++++++++++++-------------------
6 files changed, 73 insertions(+), 50 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt @@ -119,7 +119,7 @@ class MainViewModel( val refundManager = RefundManager(api, viewModelScope) val balanceManager = BalanceManager(api, viewModelScope) val exchangeManager: ExchangeManager = ExchangeManager(api, viewModelScope) - val withdrawManager = WithdrawManager(api, viewModelScope, exchangeManager) + val withdrawManager = WithdrawManager(api, viewModelScope, exchangeManager, transactionManager) val peerManager: PeerManager = PeerManager(api, exchangeManager, viewModelScope) val settingsManager: SettingsManager = SettingsManager(app.applicationContext, api, viewModelScope, balanceManager) val accountManager: AccountManager = AccountManager(api, viewModelScope) diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt @@ -82,6 +82,7 @@ class TransactionWithdrawalFragment : TransactionDetailFragment(), ActionListene if (tx !is TransactionWithdrawal) return if (tx.withdrawalDetails !is ManualTransfer) return if (tx.withdrawalDetails.exchangeCreditAccountDetails.isNullOrEmpty()) return + if (tx.exchangeBaseUrl == null) return withdrawManager.viewManualWithdrawal( transactionId = tx.transactionId, @@ -89,10 +90,10 @@ class TransactionWithdrawalFragment : TransactionDetailFragment(), ActionListene amountRaw = tx.amountRaw, amountEffective = tx.amountEffective, withdrawalAccountList = tx.withdrawalDetails.exchangeCreditAccountDetails, - scopeInfo = transactionManager.selectedScope.value ?: ScopeInfo.Exchange( - currency = tx.amountRaw.currency, - url = tx.exchangeBaseUrl, - ), + scopeInfo = transactionManager.selectedScope.value + ?: tx.exchangeBaseUrl.let { + ScopeInfo.Exchange(currency = tx.amountRaw.currency, url = it) + }, ) findNavController().navigate( diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -161,7 +161,7 @@ class TransactionWithdrawal( override val txState: TransactionState, override val txActions: List<TransactionAction>, val kycUrl: String? = null, - val exchangeBaseUrl: String, + val exchangeBaseUrl: String? = null, val withdrawalDetails: WithdrawalDetails, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt @@ -58,6 +58,7 @@ import net.taler.wallet.exchanges.ExchangeItem import net.taler.wallet.exchanges.ExchangeTosStatus import net.taler.wallet.exchanges.SelectExchangeDialogFragment import net.taler.wallet.showError +import net.taler.wallet.withdraw.WithdrawStatus.Status.AlreadyConfirmed import net.taler.wallet.withdraw.WithdrawStatus.Status.InfoReceived import net.taler.wallet.withdraw.WithdrawStatus.Status.Loading import net.taler.wallet.withdraw.WithdrawStatus.Status.ManualTransferRequired @@ -65,7 +66,7 @@ import net.taler.wallet.withdraw.WithdrawStatus.Status.None import net.taler.wallet.withdraw.WithdrawStatus.Status.Success import net.taler.wallet.withdraw.WithdrawStatus.Status.TosReviewRequired import net.taler.wallet.withdraw.WithdrawStatus.Status.Updating -import net.taler.wallet.withdraw.WithdrawalOperationStatusFlag.* +import net.taler.wallet.withdraw.WithdrawalOperationStatusFlag.Pending class PromptWithdrawFragment: Fragment() { private val model: MainViewModel by activityViewModels() @@ -110,7 +111,7 @@ class PromptWithdrawFragment: Fragment() { if (status.status == None) { if (withdrawUri != null) { // get withdrawal details for taler://withdraw URI - withdrawManager.getWithdrawalDetails(withdrawUri, loading = true) + withdrawManager.prepareBankIntegratedWithdrawal(withdrawUri, loading = true) } else if (withdrawExchangeUri != null) { // get withdrawal details for taler://withdraw-exchange URI withdrawManager.prepareManualWithdrawal(withdrawExchangeUri) @@ -140,17 +141,13 @@ class PromptWithdrawFragment: Fragment() { return@let } - if (s.uriInfo?.status == Confirmed) { - return@let - } - if (defaultCurrency == null) { LoadingScreen() return@let } when (s.status) { - Loading -> LoadingScreen() + Loading, AlreadyConfirmed -> LoadingScreen() None, InfoReceived, TosReviewRequired, Updating -> { // TODO: use scopeInfo instead of currency! @@ -198,22 +195,23 @@ class PromptWithdrawFragment: Fragment() { showError(status.error) } - if (status.uriInfo?.status == Confirmed) { - Snackbar.make(requireView(), R.string.withdraw_error_already_confirmed, LENGTH_LONG).show() - if (!navigating) { - navigating = true - findNavController().navigate(R.id.action_promptWithdraw_to_nav_main) - } - } - if (status.exchangeBaseUrl == null && selectExchangeDialog.dialog?.isShowing != true) { selectExchange() } when (status.status) { - Success, ManualTransferRequired -> lifecycleScope.launch { - Snackbar.make(requireView(), R.string.withdraw_initiated, LENGTH_LONG).show() + Success, ManualTransferRequired, AlreadyConfirmed -> lifecycleScope.launch { + Snackbar.make( + requireView(), + if (status.status == AlreadyConfirmed) { + R.string.withdraw_error_already_confirmed + } else { + R.string.withdraw_initiated + }, + LENGTH_LONG, + ).show() + status.transactionId?.let { if (!navigating) { navigating = true @@ -221,10 +219,10 @@ class PromptWithdrawFragment: Fragment() { if (transactionManager.selectTransaction(it)) { status.amountInfo?.scopeInfo?.let { s -> transactionManager.selectScope(s) } - if (status.status == Success) { - findNavController().navigate(R.id.action_promptWithdraw_to_nav_transactions_detail_withdrawal) - } else { - findNavController().navigate(R.id.action_promptWithdraw_to_nav_exchange_manual_withdrawal_success) + when (status.status) { + Success, AlreadyConfirmed -> findNavController().navigate(R.id.action_promptWithdraw_to_nav_transactions_detail_withdrawal) + ManualTransferRequired -> findNavController().navigate(R.id.action_promptWithdraw_to_nav_exchange_manual_withdrawal_success) + else -> error("unreachable") } } else { findNavController().navigate(R.id.action_promptWithdraw_to_nav_main) diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt @@ -111,10 +111,12 @@ fun TransactionWithdrawalComposable( amountType = AmountType.Positive, ) - TransactionInfoComposable( - label = stringResource(id = R.string.withdraw_exchange), - info = cleanExchange(t.exchangeBaseUrl), - ) + if (t.exchangeBaseUrl != null) { + TransactionInfoComposable( + label = stringResource(id = R.string.withdraw_exchange), + info = cleanExchange(t.exchangeBaseUrl), + ) + } TransitionsComposable(t, devMode, onTransition) diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt @@ -42,6 +42,8 @@ import net.taler.wallet.exchanges.ExchangeTosStatus import net.taler.wallet.transactions.WithdrawalExchangeAccountDetails import net.taler.wallet.withdraw.WithdrawStatus.Status.* import androidx.core.net.toUri +import net.taler.wallet.transactions.TransactionMajorState +import net.taler.wallet.transactions.TransactionManager sealed class TestWithdrawStatus { data object None : TestWithdrawStatus() @@ -73,6 +75,7 @@ data class WithdrawStatus( Loading, Updating, InfoReceived, + AlreadyConfirmed, TosReviewRequired, ManualTransferRequired, Success, @@ -138,6 +141,12 @@ enum class WithdrawalOperationStatusFlag { } @Serializable +data class PrepareBankIntegratedWithdrawalResponse( + val transactionId: String, + val info: WithdrawalDetailsForUri, +) + +@Serializable data class WithdrawalDetailsForUri( val amount: Amount? = null, val currency: String, @@ -230,6 +239,7 @@ class WithdrawManager( private val api: WalletBackendApi, private val scope: CoroutineScope, private val exchangeManager: ExchangeManager, + private val transactionManager: TransactionManager, ) { private val _withdrawStatus = MutableStateFlow(WithdrawStatus()) val withdrawStatus: StateFlow<WithdrawStatus> = _withdrawStatus.asStateFlow() @@ -266,7 +276,7 @@ class WithdrawManager( _withdrawTestStatus.value = TestWithdrawStatus.None } - fun getWithdrawalDetails( + fun prepareBankIntegratedWithdrawal( uri: String, loading: Boolean = true, ) = scope.launch { @@ -278,28 +288,40 @@ class WithdrawManager( } // first get URI details - api.request("getWithdrawalDetailsForUri", WithdrawalDetailsForUri.serializer()) { + api.request( + "prepareBankIntegratedWithdrawal", + PrepareBankIntegratedWithdrawalResponse.serializer(), + ) { put("talerWithdrawUri", uri) }.onError { error -> - handleError("getWithdrawalDetailsForUri", error) + handleError("prepareBankIntegratedWithdrawal", error) }.onSuccess { details -> Log.d(TAG, "Withdraw details: $details") - val status = _withdrawStatus.updateAndGet { value -> - value.copy( - status = InfoReceived, - uriInfo = details, - currency = details.currency, - exchangeBaseUrl = details.defaultExchangeBaseUrl, - ) - } + scope.launch { + val tx = transactionManager.getTransactionById(details.transactionId) + ?: error("transaction ${details.transactionId} not found") + val status = _withdrawStatus.updateAndGet { value -> + value.copy( + status = if (tx.txState.major == TransactionMajorState.Dialog) { + InfoReceived + } else { + AlreadyConfirmed + }, + uriInfo = details.info, + currency = details.info.currency, + exchangeBaseUrl = details.info.defaultExchangeBaseUrl, + transactionId = details.transactionId, + ) + } - // then extend with amount details (not for cash acceptor) - if (!status.isCashAcceptor) { - getWithdrawalDetails( - amount = details.amount, - exchangeBaseUrl = details.defaultExchangeBaseUrl, - loading = loading, - ) + // then extend with amount details (not for cash acceptor) + if (!status.isCashAcceptor) { + getWithdrawalDetails( + amount = details.info.amount, + exchangeBaseUrl = details.info.defaultExchangeBaseUrl, + loading = loading, + ) + } } } } @@ -498,7 +520,7 @@ class WithdrawManager( */ fun viewManualWithdrawal( transactionId: String, - exchangeBaseUrl: String, + exchangeBaseUrl: String? = null, amountRaw: Amount, amountEffective: Amount, withdrawalAccountList: List<WithdrawalExchangeAccountDetails>,