commit 75d46bd03d2f083deaf75cdf83ab9efb3fb3c7f3
parent a74af4ab88f39800cd02df7fa52efb4398123032
Author: Joel-Haeberli <haebu@rubigen.ch>
Date: Tue, 18 Mar 2025 09:44:28 +0100
improve: state handling
Diffstat:
4 files changed, 89 insertions(+), 88 deletions(-)
diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/model/Amount.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/model/Amount.kt
@@ -26,13 +26,13 @@ import kotlin.math.floor
import kotlin.math.pow
import kotlin.math.roundToInt
-public class AmountParserException(msg: String? = null, cause: Throwable? = null) :
+class AmountParserException(msg: String? = null, cause: Throwable? = null) :
Exception(msg, cause)
-public class AmountOverflowException(msg: String? = null, cause: Throwable? = null) :
+class AmountOverflowException(msg: String? = null, cause: Throwable? = null) :
Exception(msg, cause)
-public data class Amount(
+data class Amount(
/**
* name of the currency using either a three-character ISO 4217 currency code,
* or a regional currency identifier starting with a "*" followed by at most 10 characters.
@@ -61,26 +61,26 @@ public data class Amount(
val spec: CurrencySpecification? = null,
) : Comparable<Amount> {
- public companion object {
+ companion object {
private const val FRACTIONAL_BASE: Int = 100000000 // 1e8
private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""")
- public val MAX_VALUE: Long = 2.0.pow(52).toLong()
+ val MAX_VALUE: Long = 2.0.pow(52).toLong()
private const val MAX_FRACTION_LENGTH = 8
- public const val MAX_FRACTION: Int = 99_999_999
+ private const val MAX_FRACTION: Int = 99_999_999
- public fun zero(currency: String): Amount {
+ fun zero(currency: String): Amount {
return Amount(checkCurrency(currency), 0, 0)
}
- public fun fromJSONString(str: String): Amount {
+ fun fromJSONString(str: String): Amount {
val split = str.split(":")
if (split.size != 2) throw AmountParserException("Invalid Amount Format")
return fromString(split[0], split[1])
}
- public fun fromString(currency: String, str: String): Amount {
+ fun fromString(currency: String, str: String): Amount {
// value
val valueSplit = str.split(".")
val value = checkValue(valueSplit[0].toLongOrNull())
@@ -94,33 +94,12 @@ public data class Amount(
return Amount(checkCurrency(currency), value, fraction)
}
- public fun isValidAmountStr(str: String): Boolean {
- if (str.count { it == '.' } > 1) return false
- val split = str.split(".")
- try {
- checkValue(split[0].toLongOrNull())
- } catch (e: AmountParserException) {
- return false
- }
- // also check fraction, if it exists
- if (split.size > 1) {
- val fractionStr = split[1]
- if (fractionStr.length > MAX_FRACTION_LENGTH) return false
- val fraction = fractionStr.getFraction() ?: return false
- return fraction <= MAX_FRACTION
- }
- return true
- }
-
private fun String.getFraction(): Int? {
return "0.$this".toDoubleOrNull()
?.times(FRACTIONAL_BASE)
?.roundToInt()
}
- public fun min(currency: String): Amount = Amount(currency, 0, 1)
- public fun max(currency: String): Amount = Amount(currency, MAX_VALUE, MAX_FRACTION)
-
internal fun checkCurrency(currency: String): String {
if (!REGEX_CURRENCY.matches(currency))
@@ -142,9 +121,9 @@ public data class Amount(
}
- fun toBigDecimal(): BigDecimal = BigDecimal("$amountStr")
+ fun toBigDecimal(): BigDecimal = BigDecimal(amountStr)
- public val amountStr: String
+ private val amountStr: String
get() = if (fraction == 0) "$value" else {
var f = fraction
var fractionStr = ""
@@ -155,7 +134,7 @@ public data class Amount(
"$value.$fractionStr"
}
- public operator fun plus(other: Amount): Amount {
+ operator fun plus(other: Amount): Amount {
check(currency == other.currency) { "Can only subtract from same currency" }
val resultValue =
value + other.value + floor((fraction + other.fraction).toDouble() / FRACTIONAL_BASE).toLong()
@@ -165,7 +144,7 @@ public data class Amount(
return Amount(currency, resultValue, resultFraction)
}
- public operator fun times(factor: Int): Amount {
+ operator fun times(factor: Int): Amount {
// TODO consider replacing with a faster implementation
if (factor == 0) return zero(currency)
var result = this
@@ -173,13 +152,7 @@ public data class Amount(
return result
}
- public fun withCurrency(currency: String): Amount {
- return Amount(checkCurrency(currency), this.value, this.fraction)
- }
-
- fun withSpec(spec: CurrencySpecification?) = copy(spec = spec)
-
- public operator fun minus(other: Amount): Amount {
+ operator fun minus(other: Amount): Amount {
check(currency == other.currency) { "Can only subtract from same currency" }
var resultValue = value
var resultFraction = fraction
@@ -197,11 +170,7 @@ public data class Amount(
return Amount(currency, resultValue, resultFraction)
}
- public fun isZero(): Boolean {
- return value == 0L && fraction == 0
- }
-
- public fun toJSONString(): String {
+ fun toJSONString(): String {
return "$currency:$amountStr"
}
diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AuthorizePaymentScreen.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AuthorizePaymentScreen.kt
@@ -52,7 +52,7 @@ fun AuthorizePaymentScreen(model: WithdrawalViewModel, activity: Activity, clien
println("LaunchedEffect: starting authorization")
- if (uiState.transactionState == TransactionState.READY_FOR_AUTHORIZATION) {
+ if (uiState.withdrawalState == WithdrawalState.READY_FOR_AUTHORIZATION) {
val transaction = Transaction.Builder(withdrawalAmount)
.setCurrency(Currency.getInstance(uiState.currency))
.setInvoiceReference(uiState.encodedWopid)
@@ -61,7 +61,7 @@ fun AuthorizePaymentScreen(model: WithdrawalViewModel, activity: Activity, clien
.build()
try {
- client.authorizeTransaction(transaction)
+ client.authorizeTransaction(transaction) // STOPPED
model.setAuthorizing()
} catch (e: Exception) {
println("FAILED authorizing transaction ${e.message}")
@@ -77,16 +77,17 @@ fun AuthorizePaymentScreen(model: WithdrawalViewModel, activity: Activity, clien
horizontalAlignment = Alignment.CenterHorizontally
) {
- when (uiState.transactionState) {
- TransactionState.UNREADY_FOR_AUTHORIZATION -> model.withdrawalOperationFailed(activity)
- TransactionState.READY_FOR_AUTHORIZATION -> WalleeProcessingTransaction()
- TransactionState.AUTHORIZATION_TRIGGERED -> WalleeProcessingTransaction()
- TransactionState.AUTHORIZATION_FAILED -> model.withdrawalOperationFailed(activity)
+ when (uiState.withdrawalState) {
+ WithdrawalState.UNREADY_FOR_AUTHORIZATION -> model.withdrawalOperationFailed(activity)
+ WithdrawalState.READY_FOR_AUTHORIZATION -> WalleeProcessingTransaction()
+ WithdrawalState.AUTHORIZATION_TRIGGERED -> WalleeProcessingTransaction()
+ WithdrawalState.AUTHORIZATION_FAILED -> model.withdrawalOperationFailed(activity)
// TODO : Coordination of Wallee SDK components and this app must be looked at
- TransactionState.AUTHORIZED -> WalleeCompletingPayment(client, model) // TODO: Maybe following is not needed because completion immediately is specified... WalleeCompletingPayment(client, model)
- TransactionState.COMPLETION_TRIGGERED -> WalleeProcessingTransaction()
- TransactionState.COMPLETED -> model.talerCheckWalleeRequest(activity)
- TransactionState.COMPLETION_FAILED -> model.withdrawalOperationFailed(activity)
+ // -> Input Ben: Broadcast Messages abfangen (welche Broadcast sendet die SDK?)
+ WithdrawalState.AUTHORIZED -> WalleeCompletingPayment(client, model) // TODO: Maybe following is not needed because completion immediately is specified: "WalleeCompletingPayment(client, model)"
+ WithdrawalState.COMPLETION_TRIGGERED -> WalleeProcessingTransaction()
+ WithdrawalState.COMPLETED -> model.talerCheckWalleeRequest(activity)
+ WithdrawalState.COMPLETION_FAILED -> model.withdrawalOperationFailed(activity)
}
}
}
@@ -103,7 +104,7 @@ private fun WalleeCompletingPayment(
LaunchedEffect(key1 = Unit) {
- if (uiState.transactionState == TransactionState.AUTHORIZED) {
+ if (uiState.withdrawalState == WithdrawalState.AUTHORIZED) {
client.completeTransaction(
TransactionCompletion.Builder(uiState.transaction!!.transaction.lineItems)
.setCurrency(uiState.transaction!!.transaction.currency)
diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterParametersScreen.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterParametersScreen.kt
@@ -22,7 +22,6 @@ import android.app.Activity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -33,7 +32,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
-private const val MAX_RETRIES = 6 // one retry runs 10sec (long-poll) -> 6x10sec = 1min
+private const val MAX_RETRIES = 4
@Composable
fun RegisterParametersScreen(
@@ -46,7 +45,7 @@ fun RegisterParametersScreen(
val configuration = LocalConfiguration.current
LaunchedEffect(uiState.setupWithdrawalRetries) {
- // this will run exactly once -> Unit as parameter to LaunchedEffect.
+
if (ManageActivity.SIM_WALLET_ENABLED.value) {
ManageActivity.simulateParameterRegistration(
uiState.terminalsApiBasePath,
@@ -54,9 +53,8 @@ fun RegisterParametersScreen(
)
}
if (model.uiState.value.setupWithdrawalRetries < 1 &&
- model.uiState.value.transactionState == TransactionState.UNREADY_FOR_AUTHORIZATION) {
- println("starting authorization")
- model.startAuthorizationWhenReadyOrSetRetry()
+ model.uiState.value.withdrawalState == WithdrawalState.UNREADY_FOR_AUTHORIZATION) {
+ model.setAuthorizationReadyOrRetry()
}
}
@@ -66,13 +64,13 @@ fun RegisterParametersScreen(
horizontalAlignment = Alignment.CenterHorizontally
) {
- if (uiState.transactionState == TransactionState.READY_FOR_AUTHORIZATION) {
+ if (uiState.withdrawalState == WithdrawalState.READY_FOR_AUTHORIZATION) {
navigateToWhenRegistered()
return
}
- if (uiState.setupWithdrawalRetries > MAX_RETRIES &&
- uiState.transactionState == TransactionState.UNREADY_FOR_AUTHORIZATION) {
+ if (uiState.setupWithdrawalRetries > MAX_RETRIES-1 &&
+ uiState.withdrawalState == WithdrawalState.UNREADY_FOR_AUTHORIZATION) {
println("maximal retries exceeded")
model.withdrawalOperationFailed(activity)
return
diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalViewModel.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalViewModel.kt
@@ -61,7 +61,7 @@ object TalerConstants {
}
@Stable
-interface WithdrawalOperationState {
+interface Withdrawal {
val requestUid: String
val setupWithdrawalRetries: Int
val terminalsApiBasePath: String
@@ -71,13 +71,46 @@ interface WithdrawalOperationState {
val amountError: String
val currency: String
val withdrawalFees: Amount
- val transactionState: TransactionState
+ val withdrawalState: WithdrawalState
val transaction: TransactionResponse?
val transactionCompletion: TransactionCompletionResponse?
val transactionId: String
}
-enum class TransactionState {
+/**
+ * +--------------------------+
+ * | UNREADY_FOR_AUTHORIZATION |
+ * +--------------------------+
+ * |
+ * v
+ * +--------------------------+
+ * | READY_FOR_AUTHORIZATION |
+ * +--------------------------+
+ * |
+ * +-------------------------+
+ * | AUTHORIZATION_TRIGGERED |
+ * +-------------------------+
+ * |
+ * +-----------+-----------+
+ * | |
+ * v v
+ * +-------------------+ +-------------------------+
+ * | AUTHORIZED | | AUTHORIZATION_FAILED |
+ * +-------------------+ +-------------------------+
+ * |
+ * v
+ * +----------------------+
+ * | COMPLETION_TRIGGERED |
+ * +----------------------+
+ * |
+ * +-----------+-----------+
+ * | |
+ * v v
+ * +-------------------+ +-------------------------+
+ * | COMPLETED | | COMPLETION_FAILED |
+ * +-------------------+ +-------------------------+
+ */
+enum class WithdrawalState {
UNREADY_FOR_AUTHORIZATION,
READY_FOR_AUTHORIZATION,
AUTHORIZATION_TRIGGERED,
@@ -88,7 +121,7 @@ enum class TransactionState {
COMPLETED,
}
-private class MutableWithdrawalOperationState : WithdrawalOperationState {
+private class MutableWithdrawal : Withdrawal {
override val requestUid: String by derivedStateOf { UUID.randomUUID().toString() }
override var setupWithdrawalRetries: Int by mutableIntStateOf(0)
override var terminalsApiBasePath: String by mutableStateOf("")
@@ -98,7 +131,7 @@ private class MutableWithdrawalOperationState : WithdrawalOperationState {
override var amountError: String by mutableStateOf("")
override var currency: String by mutableStateOf("")
override var withdrawalFees: Amount by mutableStateOf(Amount("", 0,0))
- override var transactionState: TransactionState by mutableStateOf(TransactionState.UNREADY_FOR_AUTHORIZATION)
+ override var withdrawalState: WithdrawalState by mutableStateOf(WithdrawalState.UNREADY_FOR_AUTHORIZATION)
override var transaction: TransactionResponse? by mutableStateOf(null)
override var transactionCompletion: TransactionCompletionResponse? by mutableStateOf(null)
override val transactionId: String by derivedStateOf { generateTransactionIdentifier() }
@@ -111,8 +144,8 @@ class WithdrawalViewModel(
private var exchangeSelected = false
private var terminalClient: TerminalClient? = null
- private val _uiState = MutableStateFlow(MutableWithdrawalOperationState())
- val uiState: StateFlow<WithdrawalOperationState> = _uiState
+ private val _uiState = MutableStateFlow(MutableWithdrawal())
+ val uiState: StateFlow<Withdrawal> = _uiState
companion object {
fun generateTransactionIdentifier(): String {
@@ -131,7 +164,7 @@ class WithdrawalViewModel(
terminalClient = TerminalClientImplementation(cfg)
//terminalClient = TerminalClientMock()
- _uiState.value = MutableWithdrawalOperationState() // reset withdrawal operation
+ _uiState.value = MutableWithdrawal() // reset withdrawal operation
terminalClient!!.terminalsConfig {
println("terminal config request result present: ${it.isPresent}")
@@ -173,7 +206,7 @@ class WithdrawalViewModel(
fun validateInput(amount: String): Boolean {
- println("validating amount input: $amount")
+ //println("validating amount input: $amount")
val validAmount = parseAmount(amount)
if (validAmount.isPresent) {
_uiState.value.amountError = ""
@@ -204,22 +237,22 @@ class WithdrawalViewModel(
}
fun setAuthorizing() {
- _uiState.value.transactionState = TransactionState.AUTHORIZATION_TRIGGERED
+ _uiState.value.withdrawalState = WithdrawalState.AUTHORIZATION_TRIGGERED
}
fun setCompleting() {
- _uiState.value.transactionState = TransactionState.COMPLETION_TRIGGERED
+ _uiState.value.withdrawalState = WithdrawalState.COMPLETION_TRIGGERED
}
fun updateWalleeTransactionReply(response: TransactionResponse) {
println("updating wallee transaction: $response")
_uiState.value.transaction = response
- _uiState.value.transactionState =
+ _uiState.value.withdrawalState =
if (response.state.name == "SUCCESSFUL")
- TransactionState.AUTHORIZED
+ WithdrawalState.AUTHORIZED
else
- TransactionState.AUTHORIZATION_FAILED
+ WithdrawalState.AUTHORIZATION_FAILED
}
fun updateWalleeTransactionCompletion(
@@ -228,7 +261,7 @@ class WithdrawalViewModel(
if (completion.state == State.FAILED) {
println("completion of the transaction failed... aborting")
- _uiState.value.transactionState = TransactionState.COMPLETION_FAILED
+ _uiState.value.withdrawalState = WithdrawalState.COMPLETION_FAILED
return
}
@@ -236,18 +269,18 @@ class WithdrawalViewModel(
walleeClient.executeFinalBalance()
}
- _uiState.value.transactionState = TransactionState.COMPLETED
+ _uiState.value.withdrawalState = WithdrawalState.COMPLETED
_uiState.value.transactionCompletion = completion
}
- fun startAuthorizationWhenReadyOrSetRetry() = viewModelScope.launch {
+ fun setAuthorizationReadyOrRetry() = viewModelScope.launch {
terminalClient!!.retrieveWithdrawalStatus(uiState.value.encodedWopid, 10000) {
if (!it.isPresent) {
println("setting retry because no status was present")
_uiState.value.setupWithdrawalRetries += 1
} else {
println("withdrawal status: ${it.get().status }")
- _uiState.value.transactionState = TransactionState.READY_FOR_AUTHORIZATION
+ _uiState.value.withdrawalState = WithdrawalState.READY_FOR_AUTHORIZATION
}
}
}
@@ -280,9 +313,9 @@ class WithdrawalViewModel(
fun withdrawalOperationFailed(activity: Activity) {
viewModelScope.launch {
- if (_uiState.value.transactionState == TransactionState.UNREADY_FOR_AUTHORIZATION ||
- _uiState.value.transactionState == TransactionState.AUTHORIZATION_FAILED ||
- _uiState.value.transactionState == TransactionState.COMPLETION_FAILED) {
+ if (_uiState.value.withdrawalState == WithdrawalState.UNREADY_FOR_AUTHORIZATION ||
+ _uiState.value.withdrawalState == WithdrawalState.AUTHORIZATION_FAILED ||
+ _uiState.value.withdrawalState == WithdrawalState.COMPLETION_FAILED) {
terminalClient!!.abortWithdrawal(uiState.value.encodedWopid) {
SummaryActivity.summary =
Summary(