diff options
author | Torsten Grote <t@grobox.de> | 2020-02-12 16:53:03 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-02-12 17:09:09 -0300 |
commit | ada382885e9c103fe0795817a8585270a3079302 (patch) | |
tree | 3da610aa39d2c767307d974d54f2f4eb32be9188 /app/src/main/java/net/taler/wallet/payment | |
parent | c20a7945f2b37863264c3b9bdcc85454018bd4cd (diff) | |
download | wallet-android-ada382885e9c103fe0795817a8585270a3079302.tar.gz wallet-android-ada382885e9c103fe0795817a8585270a3079302.tar.bz2 wallet-android-ada382885e9c103fe0795817a8585270a3079302.zip |
Refactor payment code to make it easier to extend
Diffstat (limited to 'app/src/main/java/net/taler/wallet/payment')
4 files changed, 337 insertions, 0 deletions
diff --git a/app/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt b/app/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt new file mode 100644 index 0000000..62d90a5 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt @@ -0,0 +1,47 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_already_paid.* +import net.taler.wallet.R + +/** + * Display the message that the user already paid for the order + * that the merchant is proposing. + */ +class AlreadyPaidFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_already_paid, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + button_success_back.setOnClickListener { + findNavController().navigateUp() + } + } + +} diff --git a/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt new file mode 100644 index 0000000..2e40250 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -0,0 +1,108 @@ +package net.taler.wallet.payment + +import android.util.Log +import androidx.annotation.UiThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import net.taler.wallet.Amount +import net.taler.wallet.TAG +import net.taler.wallet.backend.WalletBackendApi +import org.json.JSONObject + +class PaymentManager(private val walletBackendApi: WalletBackendApi) { + + private val mPayStatus = MutableLiveData<PayStatus>(PayStatus.None) + internal val payStatus: LiveData<PayStatus> = mPayStatus + + private var currentPayRequestId = 0 + + @UiThread + fun preparePay(url: String) { + mPayStatus.value = PayStatus.Loading + + val args = JSONObject(mapOf("url" to url)) + + currentPayRequestId += 1 + val payRequestId = currentPayRequestId + + walletBackendApi.sendRequest("preparePay", args) { isError, result -> + when { + isError -> { + Log.v(TAG, "got preparePay error result") + mPayStatus.value = PayStatus.Error(result.toString()) + } + payRequestId != this.currentPayRequestId -> { + Log.v(TAG, "preparePay result was for old request") + } + else -> { + val status = result.getString("status") + mPayStatus.postValue(getPayStatusUpdate(status, result)) + } + } + } + } + + private fun getPayStatusUpdate(status: String, json: JSONObject) = when (status) { + "payment-possible" -> PayStatus.Prepared( + contractTerms = getContractTerms(json), + proposalId = json.getString("proposalId"), + totalFees = Amount.fromJson(json.getJSONObject("totalFees")) + ) + "paid" -> PayStatus.AlreadyPaid(getContractTerms(json)) + "insufficient-balance" -> PayStatus.InsufficientBalance(getContractTerms(json)) + "error" -> PayStatus.Error("got some error") + else -> PayStatus.Error("unknown status") + } + + private fun getContractTerms(json: JSONObject): ContractTerms { + val ctJson = JSONObject(json.getString("contractTermsRaw")) + val amount = Amount.fromString(ctJson.getString("amount")) + val summary = ctJson.getString("summary") + return ContractTerms(summary, amount) + } + + fun confirmPay(proposalId: String) { + val args = JSONObject(mapOf("proposalId" to proposalId)) + + walletBackendApi.sendRequest("confirmPay", args) { _, _ -> + mPayStatus.postValue(PayStatus.Success) + } + } + + fun abortProposal(proposalId: String) { + val args = JSONObject(mapOf("proposalId" to proposalId)) + + Log.i(TAG, "aborting proposal") + + walletBackendApi.sendRequest("abortProposal", args) { isError, _ -> + if (isError) { + Log.e(TAG, "received error response to abortProposal") + return@sendRequest + } + mPayStatus.postValue(PayStatus.None) + } + } + + @UiThread + fun resetPayStatus() { + mPayStatus.value = PayStatus.None + } + +} + +sealed class PayStatus { + object None : PayStatus() + object Loading : PayStatus() + data class Prepared( + val contractTerms: ContractTerms, + val proposalId: String, + val totalFees: Amount + ) : PayStatus() + + data class InsufficientBalance(val contractTerms: ContractTerms) : PayStatus() + data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus() + data class Error(val error: String) : PayStatus() + object Success : PayStatus() +} + +data class ContractTerms(val summary: String, val amount: Amount) diff --git a/app/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt b/app/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt new file mode 100644 index 0000000..608abfd --- /dev/null +++ b/app/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt @@ -0,0 +1,46 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_payment_successful.* +import net.taler.wallet.R + +/** + * Fragment that shows the success message for a payment. + */ +class PaymentSuccessfulFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_payment_successful, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + button_success_back.setOnClickListener { + findNavController().navigateUp() + } + } + +} diff --git a/app/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/app/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt new file mode 100644 index 0000000..4b4bf01 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt @@ -0,0 +1,136 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.observe +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_prompt_payment.* +import net.taler.wallet.Amount +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel + +/** + * Show a payment and ask the user to accept/decline. + */ +class PromptPaymentFragment : Fragment() { + + private val model: WalletViewModel by activityViewModels() + private val paymentManager by lazy { model.paymentManager } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_prompt_payment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + paymentManager.payStatus.observe(viewLifecycleOwner, this::onPaymentStatusChanged) + + button_abort_payment.setOnClickListener { + when (val ps = paymentManager.payStatus.value) { + is PayStatus.Prepared -> { + paymentManager.abortProposal(ps.proposalId) + } + } + paymentManager.resetPayStatus() + findNavController().navigateUp() + } + } + + private fun showLoading(show: Boolean) { + model.showProgressBar.value = show + } + + private fun onPaymentStatusChanged(payStatus: PayStatus) { + when (payStatus) { + is PayStatus.Prepared -> { + showLoading(false) + showOrder(payStatus.contractTerms, payStatus.totalFees) + button_confirm_payment.isEnabled = true + button_confirm_payment.setOnClickListener { + showLoading(true) + paymentManager.confirmPay(payStatus.proposalId) + button_confirm_payment.isEnabled = false + } + } + is PayStatus.InsufficientBalance -> { + showLoading(false) + showOrder(payStatus.contractTerms, null) + error_text.setText(R.string.payment_balance_insufficient) + fadeInView(error_text) + } + is PayStatus.Success -> { + showLoading(false) + paymentManager.resetPayStatus() + findNavController().navigate(R.id.action_promptPayment_to_paymentSuccessful) + } + is PayStatus.AlreadyPaid -> { + showLoading(false) + paymentManager.resetPayStatus() + findNavController().navigate(R.id.action_promptPayment_to_alreadyPaid) + } + is PayStatus.Error -> { + showLoading(false) + error_text.text = getString(R.string.payment_error, payStatus.error) + fadeInView(error_text) + } + is PayStatus.None -> { + // No payment active. + showLoading(false) + } + is PayStatus.Loading -> { + // Wait until loaded ... + showLoading(true) + } + } + } + + private fun showOrder(contractTerms: ContractTerms, totalFees: Amount?) { + order_summary.text = contractTerms.summary + val amount = contractTerms.amount + @SuppressLint("SetTextI18n") + order_amount.text = "${amount.amount} ${amount.currency}" + if (totalFees != null && !totalFees.isZero()) { + val fee = "${totalFees.amount} ${totalFees.currency}" + order_fees_amount.text = getString(R.string.payment_fee, fee) + fadeInView(order_fees_amount) + } else { + order_fees_amount.visibility = INVISIBLE + } + fadeInView(order_summary_label) + fadeInView(order_summary) + fadeInView(order_amount_label) + fadeInView(order_amount) + } + + private fun fadeInView(v: View) { + v.alpha = 0f + v.visibility = VISIBLE + v.animate().alpha(1f).start() + } + +} |