From f004af7b746a436e7317d5c27f8f261bd7a407f0 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Tue, 30 Jan 2024 18:55:55 -0600 Subject: [wallet] Redirect to details view after payment, deprecate proposalId, and remove pending op actions bug 0008297 --- .../taler/wallet/payment/PayTemplateComposable.kt | 2 +- .../net/taler/wallet/payment/PaymentManager.kt | 71 +++++++++++----------- .../net/taler/wallet/payment/PaymentResponses.kt | 19 +++--- .../taler/wallet/payment/PromptPaymentFragment.kt | 35 +++++++++-- .../wallet/pending/PendingOperationsFragment.kt | 35 ----------- .../res/layout/list_item_pending_operation.xml | 6 -- wallet/src/main/res/navigation/nav_graph.xml | 4 ++ 7 files changed, 81 insertions(+), 91 deletions(-) (limited to 'wallet') diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt index 815f463..e2a8a64 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt @@ -149,7 +149,7 @@ fun PayTemplateAlreadyPaidPreview() { PayTemplateComposable( defaultSummary = "Donation", amountStatus = AmountFieldStatus.Default("20", "ARS"), - payStatus = PayStatus.AlreadyPaid, + payStatus = PayStatus.AlreadyPaid(transactionId = "transactionId"), currencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt index 3a3069c..19be280 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -41,7 +41,7 @@ sealed class PayStatus { object Loading : PayStatus() data class Prepared( val contractTerms: ContractTerms, - val proposalId: String, + val transactionId: String, val amountRaw: Amount, val amountEffective: Amount, ) : PayStatus() @@ -51,10 +51,18 @@ sealed class PayStatus { val amountRaw: Amount, ) : PayStatus() - // TODO bring user to fulfilment URI - object AlreadyPaid : PayStatus() - data class Error(val error: TalerErrorInfo) : PayStatus() - data class Success(val currency: String) : PayStatus() + data class AlreadyPaid( + val transactionId: String, + ) : PayStatus() + + data class Error( + val transactionId: String? = null, + val error: TalerErrorInfo, + ) : PayStatus() + data class Success( + val transactionId: String, + val currency: String, + ) : PayStatus() } class PaymentManager( @@ -76,32 +84,33 @@ class PaymentManager( mPayStatus.value = when (response) { is PaymentPossibleResponse -> response.toPayStatusPrepared() is InsufficientBalanceResponse -> InsufficientBalance( - response.contractTerms, - response.amountRaw + contractTerms = response.contractTerms, + amountRaw = response.amountRaw + ) + is AlreadyConfirmedResponse -> AlreadyPaid( + transactionId = response.transactionId, ) - - is AlreadyConfirmedResponse -> AlreadyPaid } } } - fun confirmPay(proposalId: String, currency: String) = scope.launch { + fun confirmPay(transactionId: String, currency: String) = scope.launch { api.request("confirmPay", ConfirmPayResult.serializer()) { - put("proposalId", proposalId) + put("transactionId", transactionId) }.onError { handleError("confirmPay", it) - }.onSuccess { - mPayStatus.postValue(PayStatus.Success(currency)) - } - } - - @UiThread - fun abortPay() { - val ps = payStatus.value - if (ps is PayStatus.Prepared) { - abortProposal(ps.proposalId) + }.onSuccess { response -> + mPayStatus.postValue(when (response) { + is ConfirmPayResult.Done -> PayStatus.Success( + transactionId = response.transactionId, + currency = currency, + ) + is ConfirmPayResult.Pending -> PayStatus.Error( + transactionId = response.transactionId, + error = response.lastError, + ) + }) } - resetPayStatus() } fun preparePayForTemplate(url: String, summary: String?, amount: Amount?) = scope.launch { @@ -122,23 +131,13 @@ class PaymentManager( amountRaw = response.amountRaw, ) - is AlreadyConfirmedResponse -> AlreadyPaid + is AlreadyConfirmedResponse -> AlreadyPaid( + transactionId = response.transactionId, + ) } } } - internal fun abortProposal(proposalId: String) = scope.launch { - Log.i(TAG, "aborting proposal") - api.request("abortProposal") { - put("proposalId", proposalId) - }.onError { - Log.e(TAG, "received error response to abortProposal") - handleError("abortProposal", it) - }.onSuccess { - mPayStatus.postValue(PayStatus.None) - } - } - @UiThread fun resetPayStatus() { mPayStatus.value = PayStatus.None @@ -146,7 +145,7 @@ class PaymentManager( private fun handleError(operation: String, error: TalerErrorInfo) { Log.e(TAG, "got $operation error result $error") - mPayStatus.value = PayStatus.Error(error) + mPayStatus.value = PayStatus.Error(error = error) } } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt index 7e03472..5e97f58 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt @@ -32,14 +32,14 @@ sealed class PreparePayResponse { @Serializable @SerialName("payment-possible") data class PaymentPossibleResponse( - val proposalId: String, + val transactionId: String, val amountRaw: Amount, val amountEffective: Amount, val contractTerms: ContractTerms, ) : PreparePayResponse() { fun toPayStatusPrepared() = PayStatus.Prepared( contractTerms = contractTerms, - proposalId = proposalId, + transactionId = transactionId, amountRaw = amountRaw, amountEffective = amountEffective, ) @@ -48,7 +48,6 @@ sealed class PreparePayResponse { @Serializable @SerialName("insufficient-balance") data class InsufficientBalanceResponse( - val proposalId: String, val amountRaw: Amount, val contractTerms: ContractTerms, ) : PreparePayResponse() @@ -56,13 +55,13 @@ sealed class PreparePayResponse { @Serializable @SerialName("already-confirmed") data class AlreadyConfirmedResponse( - val proposalId: String, + val transactionId: String, /** * Did the payment succeed? */ val paid: Boolean, val amountRaw: Amount, - val amountEffective: Amount, + val amountEffective: Amount? = null, val contractTerms: ContractTerms, ) : PreparePayResponse() } @@ -71,9 +70,15 @@ sealed class PreparePayResponse { sealed class ConfirmPayResult { @Serializable @SerialName("done") - data class Done(val contractTerms: ContractTerms) : ConfirmPayResult() + data class Done( + val transactionId: String, + val contractTerms: ContractTerms, + ) : ConfirmPayResult() @Serializable @SerialName("pending") - data class Pending(val lastError: TalerErrorInfo) : ConfirmPayResult() + data class Pending( + val transactionId: String, + val lastError: TalerErrorInfo, + ) : ConfirmPayResult() } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt index 609adb4..73da394 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt @@ -18,22 +18,27 @@ package net.taler.wallet.payment import android.graphics.Bitmap import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG +import kotlinx.coroutines.launch import net.taler.common.Amount import net.taler.common.ContractTerms import net.taler.common.fadeIn import net.taler.common.fadeOut +import net.taler.common.showError import net.taler.wallet.MainViewModel import net.taler.wallet.R +import net.taler.wallet.TAG import net.taler.wallet.databinding.FragmentPromptPaymentBinding import net.taler.wallet.showError @@ -44,6 +49,7 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { private val model: MainViewModel by activityViewModels() private val paymentManager by lazy { model.paymentManager } + private val transactionManager by lazy { model.transactionManager } private lateinit var ui: FragmentPromptPaymentBinding private val adapter = ProductAdapter(this) @@ -68,7 +74,15 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { override fun onDestroy() { super.onDestroy() if (!requireActivity().isChangingConfigurations) { - paymentManager.abortPay() + val payStatus = paymentManager.payStatus.value as? PayStatus.Prepared ?: return + transactionManager.abortTransaction(payStatus.transactionId) { error -> + Log.e(TAG, "Error abortTransaction $error") + if (model.devMode.value == false) { + showError(error.userFacingMsg) + } else { + showError(error) + } + } } } @@ -92,8 +106,8 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { ui.bottom.confirmButton.setOnClickListener { model.showProgressBar.value = true paymentManager.confirmPay( - payStatus.proposalId, - payStatus.contractTerms.amount.currency + transactionId = payStatus.transactionId, + currency = payStatus.contractTerms.amount.currency, ) ui.bottom.confirmButton.fadeOut() ui.bottom.confirmProgressBar.fadeIn() @@ -108,14 +122,14 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { is PayStatus.Success -> { showLoading(false) paymentManager.resetPayStatus() - findNavController().navigate(R.id.action_promptPayment_to_nav_main) - model.showTransactions(payStatus.currency) + navigateToTransaction(payStatus.transactionId) Snackbar.make(requireView(), R.string.payment_initiated, LENGTH_LONG).show() } is PayStatus.AlreadyPaid -> { showLoading(false) paymentManager.resetPayStatus() - findNavController().navigate(R.id.action_promptPayment_to_alreadyPaid) + navigateToTransaction(payStatus.transactionId) + Snackbar.make(requireView(), R.string.payment_already_paid, LENGTH_LONG).show() } is PayStatus.Error -> { showLoading(false) @@ -158,4 +172,13 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { f.show(parentFragmentManager, "image") } + private fun navigateToTransaction(id: String) { + lifecycleScope.launch { + if (transactionManager.selectTransaction(id)) { + findNavController().navigate(R.id.action_promptPayment_to_nav_transactions_detail_payment) + } else { + findNavController().navigate(R.id.action_promptPayment_to_nav_main) + } + } + } } diff --git a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt index 6bfcf90..a76a7d1 100644 --- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt @@ -17,14 +17,11 @@ package net.taler.wallet.pending import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView @@ -36,13 +33,11 @@ import androidx.recyclerview.widget.RecyclerView import net.taler.common.showError import net.taler.wallet.MainViewModel import net.taler.wallet.R -import net.taler.wallet.TAG import net.taler.wallet.databinding.FragmentPendingOperationsBinding import org.json.JSONObject interface PendingOperationClickListener { fun onPendingOperationClick(type: String, detail: JSONObject) - fun onPendingOperationActionClick(type: String, detail: JSONObject) } class PendingOperationsFragment : Fragment(), PendingOperationClickListener { @@ -113,20 +108,6 @@ class PendingOperationsFragment : Fragment(), PendingOperationClickListener { override fun onPendingOperationClick(type: String, detail: JSONObject) { requireActivity().showError("No detail view for $type implemented yet.") } - - override fun onPendingOperationActionClick(type: String, detail: JSONObject) { - when (type) { - "proposal-choice" -> { - Log.v(TAG, "got action click on proposal-choice") - val proposalId = detail.optString("proposalId", "") - if (proposalId == "") { - return - } - model.paymentManager.abortProposal(proposalId) - } - } - } - } class PendingOperationsAdapter( @@ -155,22 +136,6 @@ class PendingOperationsAdapter( pendingContainer.setOnClickListener { listener.onPendingOperationClick(p.type, p.detail) } - when (p.type) { - "proposal-choice" -> { - val btn1 = holder.rowView.findViewById(R.id.button_pending_action_1) - btn1.text = btn1.context.getString(R.string.pending_operations_refuse) - btn1.visibility = VISIBLE - btn1.setOnClickListener { - listener.onPendingOperationActionClick(p.type, p.detail) - } - } - else -> { - val btn1 = holder.rowView.findViewById(R.id.button_pending_action_1) - btn1.text = btn1.context.getString(R.string.pending_operations_no_action) - btn1.visibility = GONE - btn1.setOnClickListener {} - } - } val textView = holder.rowView.findViewById(R.id.pending_text) val subTextView = holder.rowView.findViewById(R.id.pending_subtext) subTextView.text = p.detail.toString(1) diff --git a/wallet/src/main/res/layout/list_item_pending_operation.xml b/wallet/src/main/res/layout/list_item_pending_operation.xml index bd606c3..210715c 100644 --- a/wallet/src/main/res/layout/list_item_pending_operation.xml +++ b/wallet/src/main/res/layout/list_item_pending_operation.xml @@ -31,12 +31,6 @@ android:textSize="24sp" tools:text="My Pending Operation" /> -