taler-android

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

commit 761aa68df2fe364d9351085f3a3081a99e60068f
parent 93aa3afcd9c2985f1442221be7f4794e5743e05d
Author: Iván Ávalos <avalos@disroot.org>
Date:   Tue, 29 Apr 2025 17:51:40 +0200

[wallet] show dialog from history for payments in dialog state

bug 0009787

Diffstat:
Mtaler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt | 2+-
Mwallet/src/main/java/net/taler/wallet/MainFragment.kt | 31+++++++++++++++++++++++++++----
Mwallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mwallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt | 2+-
Mwallet/src/main/java/net/taler/wallet/transactions/Transactions.kt | 2++
5 files changed, 98 insertions(+), 6 deletions(-)

diff --git a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt @@ -38,7 +38,7 @@ data class ContractTerms( val fulfillmentMessage: String? = null, @SerialName("fulfillment_message_i18n") val fulfillmentMessageI18n: Map <String, String>? = null, - val products: List<ContractProduct>, + val products: List<ContractProduct>? = null, @SerialName("wire_transfer_deadline") val wireTransferDeadline: Timestamp? = null, @SerialName("refund_deadline") diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt b/wallet/src/main/java/net/taler/wallet/MainFragment.kt @@ -89,6 +89,10 @@ import net.taler.wallet.compose.GridMenuItem import net.taler.wallet.compose.TalerSurface import net.taler.wallet.compose.collectAsStateLifecycleAware import net.taler.wallet.settings.SettingsFragment +import net.taler.wallet.transactions.Transaction +import net.taler.wallet.transactions.TransactionMajorState +import net.taler.wallet.transactions.TransactionPayment +import net.taler.wallet.transactions.TransactionState import net.taler.wallet.transactions.TransactionStateFilter.Nonfinal import kotlin.math.roundToInt @@ -194,10 +198,7 @@ class MainFragment: Fragment() { model.showTransactions(it.scopeInfo, Nonfinal) }, onTransactionClicked = { tx -> - if (tx.detailPageNav != 0) { - model.transactionManager.selectTransaction(tx) - findNavController().navigate(tx.detailPageNav) - } + onTransactionClicked(tx) }, onTransactionsDelete = { txIds -> model.transactionManager.deleteTransactions(txIds) { error -> @@ -237,7 +238,29 @@ class MainFragment: Fragment() { } } + private fun onTransactionClicked(tx: Transaction) { + val showTxDetails = { + if (tx.detailPageNav != 0) { + model.transactionManager.selectTransaction(tx) + findNavController().navigate(tx.detailPageNav) + } + } + + when (tx.txState) { + // unfinished transactions (dialog) + TransactionState(TransactionMajorState.Dialog) -> when (tx) { + is TransactionPayment -> { + model.paymentManager.preparePay(tx) { + findNavController().navigate(R.id.action_global_promptPayment) + } + } + + else -> showTxDetails() + } + else -> showTxDetails() + } + } override fun onStart() { super.onStart() diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -22,8 +22,11 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.JsonClassDiscriminator import net.taler.common.Amount import net.taler.common.ContractTerms import net.taler.wallet.TAG @@ -35,6 +38,7 @@ import net.taler.wallet.payment.PayStatus.InsufficientBalance import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse +import net.taler.wallet.transactions.TransactionPayment import org.json.JSONObject sealed class PayStatus { @@ -78,6 +82,31 @@ data class CheckPayTemplateResponse( val supportedCurrencies: List<String>, ) +@Serializable +data class GetChoicesForPaymentResponse( + val choices: List<ChoiceSelectionDetail>, + val contractData: ContractTerms, +) { + @Serializable + @OptIn(ExperimentalSerializationApi::class) + @JsonClassDiscriminator("status") + sealed class ChoiceSelectionDetail { + @Serializable + @SerialName("payment-possible") + data class PaymentPossible( + val amountRaw: Amount, + val amountEffective: Amount, + ) : ChoiceSelectionDetail() + + @Serializable + @SerialName("insufficient-balance") + data class InsufficientBalance( + val amountRaw: Amount, + val balanceDetails: PaymentInsufficientBalanceDetails? = null, + ) : ChoiceSelectionDetail() + } +} + class PaymentManager( private val api: WalletBackendApi, private val scope: CoroutineScope, @@ -108,6 +137,44 @@ class PaymentManager( } } + @UiThread + fun preparePay( + tx: TransactionPayment, + onSuccess: () -> Unit, + ) = scope.launch { + api.request("getChoicesForPayment", GetChoicesForPaymentResponse.serializer()) { + put("transactionId", tx.transactionId) + }.onSuccess { res -> + // TODO: this is a terrible v0-only hack! + if (res.choices.size == 1) { + when (val choice = res.choices[0]) { + is GetChoicesForPaymentResponse.ChoiceSelectionDetail.PaymentPossible -> { + mPayStatus.value = PayStatus.Prepared( + transactionId = tx.transactionId, + amountRaw = choice.amountRaw, + amountEffective = choice.amountEffective, + contractTerms = res.contractData, + ) + onSuccess() + } + + is GetChoicesForPaymentResponse.ChoiceSelectionDetail.InsufficientBalance -> { + if (choice.balanceDetails != null) { + mPayStatus.value = InsufficientBalance( + amountRaw = choice.amountRaw, + contractTerms = res.contractData, + balanceDetails = choice.balanceDetails, + ) + onSuccess() + } + } + } + } + }.onError { error -> + handleError("getChoicesForPayment", error) + } + } + fun confirmPay(transactionId: String, currency: String) = scope.launch { api.request("confirmPay", ConfirmPayResult.serializer()) { put("transactionId", transactionId) diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt @@ -180,7 +180,7 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { totalFees: Amount? = null, ) { ui.details.orderView.text = contractTerms.summary - adapter.update(contractTerms.products) + adapter.update(contractTerms.products ?: emptyList()) ui.details.productsList.fadeIn() ui.bottom.totalView.text = amount.toString() diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -37,6 +37,7 @@ import kotlinx.serialization.json.JsonElement import net.taler.common.Amount import net.taler.common.ContractMerchant import net.taler.common.ContractProduct +import net.taler.common.ContractTerms import net.taler.common.Timestamp import net.taler.wallet.R import net.taler.wallet.TAG @@ -309,6 +310,7 @@ class TransactionPayment( override val txState: TransactionState, override val txActions: List<TransactionAction>, val info: TransactionInfo, + val contractTerms: ContractTerms? = null, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, override val amountEffective: Amount,