summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler/wallet/payment
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-08-11 23:08:46 +0200
committerTorsten Grote <t@grobox.de>2023-09-26 18:30:51 +0200
commitcb6d8362746481b383d559280c8cfadbed082231 (patch)
treef5f3d5c0349a646e1148f06a270fe18aac62e4a6 /wallet/src/main/java/net/taler/wallet/payment
parentd7196a07eaaf5ca52906a8c987144c7b9814dfef (diff)
downloadtaler-android-cb6d8362746481b383d559280c8cfadbed082231.tar.gz
taler-android-cb6d8362746481b383d559280c8cfadbed082231.tar.bz2
taler-android-cb6d8362746481b383d559280c8cfadbed082231.zip
[wallet] Initial version of template support
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/payment')
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt226
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt21
2 files changed, 247 insertions, 0 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt
new file mode 100644
index 0000000..ab6dada
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt
@@ -0,0 +1,226 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2023 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.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
+import net.taler.common.Amount
+import net.taler.common.AmountParserException
+import net.taler.common.showError
+import net.taler.wallet.AmountResult
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+import net.taler.wallet.compose.TalerSurface
+import net.taler.wallet.deposit.CurrencyDropdown
+
+class PayTemplateFragment: Fragment() {
+ private val model: MainViewModel by activityViewModels()
+ private var uriString: String? = null
+ private var uri: Uri? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ uriString = arguments?.getString("uri") ?: error("no amount passed")
+ uri = Uri.parse(uriString)
+ val currencies = model.getCurrencies()
+
+ return ComposeView(requireContext()).apply {
+ setContent {
+ TalerSurface {
+ PayTemplateComposable(
+ uri = uri!!,
+ currencies = currencies,
+ fragment = this@PayTemplateFragment,
+ model = model,
+ onSubmit = { createOrder(it) },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ if (uri?.queryParameterNames?.isEmpty() == true) {
+ createOrder(emptyMap())
+ }
+ }
+
+ fun createOrder(params: Map<String, String>) {
+ uriString?.let {
+ model.paymentManager.preparePayForTemplate(it, params,).invokeOnCompletion {
+ findNavController().navigate(R.id.action_global_promptPayment)
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PayTemplateComposable(
+ uri: Uri,
+ currencies: List<String>,
+ fragment: Fragment,
+ model: MainViewModel,
+ onSubmit: (Map<String, String>) -> Unit,
+) {
+ val queryParams = uri.queryParameterNames
+
+ var summary by remember { mutableStateOf(
+ if ("summary" in queryParams)
+ uri.getQueryParameter("summary") else null,
+ ) }
+
+ var amount by remember { mutableStateOf(
+ if ("amount" in queryParams) {
+ val amount = uri.getQueryParameter("amount")!!
+ val parts = amount.split(':')
+ when (parts.size) {
+ 1 -> Amount.fromString(parts[0], "0")
+ 2 -> Amount.fromString(parts[0], parts[1])
+ else -> throw AmountParserException("Invalid Amount Format")
+ }
+ } else null,
+ ) }
+
+ Column(horizontalAlignment = Alignment.End) {
+ if ("summary" in queryParams) {
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ value = summary!!,
+ isError = summary!!.isBlank(),
+ onValueChange = { summary = it },
+ singleLine = true,
+ label = { Text(stringResource(R.string.withdraw_manual_ready_subject)) },
+ )
+ }
+
+ if ("amount" in queryParams) {
+ AmountField(
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ amount = amount!!,
+ currencies = currencies,
+ onAmountChosen = { amount = it },
+ )
+ }
+
+ Button(
+ modifier = Modifier.padding(16.dp),
+ enabled = summary == null || summary!!.isNotBlank(),
+ onClick = {
+ if (amount != null) {
+ val result = model.createAmount(
+ amount!!.amountStr,
+ amount!!.currency,
+ )
+ when (result) {
+ AmountResult.InsufficientBalance -> {
+ fragment.showError(R.string.payment_balance_insufficient)
+ }
+ AmountResult.InvalidAmount -> {
+ fragment.showError(R.string.receive_amount_invalid)
+ }
+ else -> {
+ onSubmit(
+ mutableMapOf<String, String>().apply {
+ if (summary != null) put("summary", summary!!)
+ if (amount != null) put("amount", amount!!.toJSONString())
+ }
+ )
+ }
+ }
+ }
+ },
+ ) {
+ Text(stringResource(R.string.payment_create_order))
+ }
+ }
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun AmountField(
+ modifier: Modifier = Modifier,
+ currencies: List<String>,
+ amount: Amount,
+ onAmountChosen: (Amount) -> Unit,
+) {
+ Row(
+ modifier = modifier,
+ ) {
+ val amountText = if (amount.value == 0L) "" else amount.value.toString()
+ val currency = currencies.find { amount.currency == it } ?: currencies[0]
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(end = 16.dp)
+ .weight(1f),
+ value = amountText,
+ placeholder = { Text("0") },
+ onValueChange = { input ->
+ if (input.isNotBlank()) {
+ onAmountChosen(Amount.fromString(currency, input))
+ } else {
+ onAmountChosen(Amount.zero(currency))
+ }
+ },
+ keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Decimal),
+ singleLine = true,
+ label = { Text(stringResource(R.string.send_amount)) },
+ )
+ CurrencyDropdown(
+ modifier = Modifier.weight(1f),
+ initialCurrency = currency,
+ currencies = currencies,
+ onCurrencyChanged = { c ->
+ onAmountChosen(Amount.fromString(c, amount.amountStr))
+ },
+ )
+ }
+} \ No newline at end of file
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 c280304..538f2e1 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -32,6 +32,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 org.json.JSONObject
val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
@@ -102,6 +103,26 @@ class PaymentManager(
resetPayStatus()
}
+ fun preparePayForTemplate(url: String, params: Map<String, String>) = scope.launch {
+ api.request("preparePayForTemplate", PreparePayResponse.serializer()) {
+ put("talerPayTemplateUri", url)
+ put("templateParams", JSONObject().apply {
+ params.forEach { put(it.key, it.value) }
+ })
+ }.onError {
+ handleError("preparePayForTemplate", it)
+ }.onSuccess { response ->
+ mPayStatus.value = when (response) {
+ is PaymentPossibleResponse -> response.toPayStatusPrepared()
+ is InsufficientBalanceResponse -> InsufficientBalance(
+ response.contractTerms,
+ response.amountRaw
+ )
+ is AlreadyConfirmedResponse -> AlreadyPaid
+ }
+ }
+ }
+
internal fun abortProposal(proposalId: String) = scope.launch {
Log.i(TAG, "aborting proposal")
api.request<Unit>("abortProposal") {