commit 12a73ee0648fcacadcc7b3c269ac88d96044b41b parent 9ac89b2db43decf796b27d37967fcc6dab2469ae Author: Joel-Haeberli <haebu@rubigen.ch> Date: Thu, 9 May 2024 12:04:06 +0200 fix: app flow Diffstat:
8 files changed, 204 insertions(+), 69 deletions(-)
diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/wallee/WalleeResponseHandler.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/wallee/WalleeResponseHandler.kt @@ -2,7 +2,6 @@ package ch.bfh.habej2.wallee_c2ec.client.wallee import android.app.Activity import ch.bfh.habej2.wallee_c2ec.withdrawal.WithdrawalViewModel -import com.wallee.android.till.sdk.ApiClient import com.wallee.android.till.sdk.ResponseHandler import com.wallee.android.till.sdk.data.FinalBalanceResult import com.wallee.android.till.sdk.data.TransactionCompletionResponse 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 @@ -43,10 +43,36 @@ fun AuthorizePaymentScreen(model: WithdrawalViewModel, activity: Activity, clien horizontalAlignment = Alignment.CenterHorizontally ) { - if (uiState.transaction == null) { + if (uiState.authorizationState == AuthorizationState.AUTHORIZATION_PENDING) { Text(text = "Authorizing transaction...") + +// val withdrawalAmount = LineItem +// .ListBuilder( +// uiState.encodedWopid, +// BigDecimal("3.0") +// ) +// .build() + + val transaction = Transaction.Builder(withdrawalAmount) + .setCurrency(Currency.getInstance(uiState.currency)) + .setInvoiceReference(uiState.encodedWopid) + .setMerchantReference(uiState.encodedWopid) + .setTransactionProcessingBehavior(TransactionProcessingBehavior.COMPLETE_IMMEDIATELY) + .build() + + try { + model.setAuthorizing() + client.authorizeTransaction(transaction) + //client.completeTransaction(TransactionCompletion.Builder(transaction.lineItems).build()) + } catch (e: Exception) { + println("FAILED authorizing transaction ${e.message}") + model.withdrawalOperationFailed() + activity.finish() + e.printStackTrace() + } + Button(onClick = { model.withdrawalOperationFailed() activity.finish() @@ -74,30 +100,6 @@ fun AuthorizePaymentScreen(model: WithdrawalViewModel, activity: Activity, clien } } } - -// val withdrawalAmount = LineItem -// .ListBuilder( -// uiState.encodedWopid, -// BigDecimal("3.0") -// ) -// .build() - - val transaction = Transaction.Builder(withdrawalAmount) - .setCurrency(Currency.getInstance(uiState.currency)) - .setInvoiceReference(uiState.encodedWopid) - .setMerchantReference(uiState.encodedWopid) - .setTransactionProcessingBehavior(TransactionProcessingBehavior.COMPLETE_IMMEDIATELY) - .build() - - try { - client.authorizeTransaction(transaction) - //client.completeTransaction(TransactionCompletion.Builder(transaction.lineItems).build()) - } catch (e: Exception) { - println("FAILED authorizing transaction ${e.message}") - model.withdrawalOperationFailed() - activity.finish() - e.printStackTrace() - } } //@Preview diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ManageActivity.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ManageActivity.kt @@ -13,13 +13,58 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import ch.bfh.habej2.wallee_c2ec.client.wallee.WalleeResponseHandler import ch.bfh.habej2.wallee_c2ec.ui.theme.Walleec2ecTheme +import com.squareup.moshi.Json +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.wallee.android.till.sdk.ApiClient +import kotlinx.coroutines.asCoroutineDispatcher +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import java.util.concurrent.Executors class ManageActivity : ComponentActivity() { private lateinit var walleeClient: ApiClient private var unbound = false + + companion object { + + private data class Reg( + @Json(name = "reserve_pub") val reservePubKey: String, + @Json(name = "selected_exchange") val selectedExchange: String + ) + + // this is only for testing and simulates a wallet registering parameters + fun simulateParameterRegistration( + exchangeBankIntegrationApiUrl: String, + wopid: String + ) { + println("simulating wallet parameter selection") + // we will just use the wopid as reserve pub key... + val reg = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + .adapter(Reg::class.java) + .toJson(Reg(wopid, exchangeBankIntegrationApiUrl)) + .toRequestBody("application/json".toMediaType()) + Executors.newSingleThreadExecutor().asCoroutineDispatcher().executor.execute { + val url = "$exchangeBankIntegrationApiUrl/withdrawal-operation/$wopid" + println("requesting $url") + val req = Request.Builder() + .post(reg) + .url(url) + .build() + OkHttpClient.Builder().build().newCall(req).execute().use { + println("registered parameters: HTTP ${it.code}") + } + } + } + + var SIM_WALLET_ENABLED = true + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -42,6 +87,15 @@ class ManageActivity : ComponentActivity() { TestTransactionScreen(activity = this@ManageActivity) Button(onClick = { + SIM_WALLET_ENABLED = !SIM_WALLET_ENABLED + println("toggle sim wallet: $SIM_WALLET_ENABLED") + }) { + Text(text = "toggle wallet simulation") + } + + Text(text = "sim wallet enabled? $SIM_WALLET_ENABLED") + + Button(onClick = { onDestroy() this@ManageActivity.finish() }) { 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 @@ -13,11 +13,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import ch.bfh.habej2.wallee_c2ec.client.wallee.WalleeResponseHandler -import com.wallee.android.till.sdk.ApiClient @SuppressLint("StateFlowValueCalledInComposition") @Composable @@ -40,6 +36,7 @@ fun RegisterParametersScreen( Text(text = "register the withdrawal parameters") // Text(text = "QR-Code content: ${TalerConstants.formatTalerUri(uiState.exchangeBankIntegrationApiUrl, uiState.encodedWopid)}") + println("QR-CODE: ${TalerConstants.formatTalerUri(uiState.exchangeBankIntegrationApiUrl, uiState.encodedWopid)}") QRCode( TalerConstants.formatTalerUri( uiState.exchangeBankIntegrationApiUrl, @@ -49,8 +46,23 @@ fun RegisterParametersScreen( AbortButton(model = model, activity = activity) - model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { - activity.finish() + if (ManageActivity.SIM_WALLET_ENABLED) { + Button(onClick = { + ManageActivity.simulateParameterRegistration( + uiState.exchangeBankIntegrationApiUrl, + uiState.encodedWopid + ) + + model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { + activity.finish() + } + }) { + Text(text = "simulate registration") + } + } else { + model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { + activity.finish() + } } } } diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/SummaryActivity.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/SummaryActivity.kt @@ -0,0 +1,41 @@ +//package ch.bfh.habej2.wallee_c2ec.withdrawal +// +//import android.os.Bundle +//import androidx.activity.ComponentActivity +//import androidx.activity.compose.setContent +//import androidx.compose.foundation.background +//import androidx.compose.material3.Button +//import androidx.compose.material3.Text +//import androidx.compose.runtime.collectAsState +//import androidx.compose.ui.Modifier +//import androidx.compose.ui.graphics.Color +//import androidx.compose.ui.unit.dp +// +//class SummaryActivity(private val model: WithdrawalViewModel): ComponentActivity() { +// +// override fun onCreate(savedInstanceState: Bundle?) { +// super.onCreate(savedInstanceState) +// +// setContent { +// +// val uiState = model.uiState.collectAsState() +// +// Text( +// text = "Amount authorized", +// color = Color.Yellow, +// modifier = Modifier.background(color = Color.Black) +// ) +// Text(text = "Summary") +// Text(text = "Withdrawable Amount: ${uiState.amount} ${uiState.currency}") +// Text(text = "Fees: 0.30 ${uiState.currency}") // TODO fees +// Text(text = "Withdrawal Operation ID (QR Code):") +// QRCode(qrCodeContent = uiState.encodedWopid, 2.dp) +// +// Button(onClick = { +// finish() +// }) { +// Text(text = "finish") +// } +// } +// } +//} +\ No newline at end of file diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/TestTransactionScreen.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/TestTransactionScreen.kt @@ -17,7 +17,10 @@ import java.util.UUID @Composable fun TestTransactionScreen(activity: Activity) { - val walleeClient = ApiClient(WalleeResponseHandler(activity, WithdrawalViewModel())) + val walleeClient = ApiClient(WalleeResponseHandler( + activity, + WithdrawalViewModel()) + ) walleeClient.bind(activity) walleeClient.checkApiServiceCompatibility() diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalActivity.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalActivity.kt @@ -1,5 +1,6 @@ package ch.bfh.habej2.wallee_c2ec.withdrawal +import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -9,6 +10,7 @@ import androidx.navigation.compose.rememberNavController import ch.bfh.habej2.wallee_c2ec.client.wallee.WalleeResponseHandler import com.wallee.android.till.sdk.ApiClient + class WithdrawalActivity : ComponentActivity() { private lateinit var walleeClient: ApiClient @@ -42,10 +44,20 @@ class WithdrawalActivity : ComponentActivity() { AuthorizePaymentScreen(model, this@WithdrawalActivity, walleeClient) } } + + walleeClient = ApiClient(WalleeResponseHandler(this, model)) + walleeClient.bind(this) + walleeClient.checkApiServiceCompatibility() } + } - walleeClient = ApiClient(WalleeResponseHandler(this, model)) - walleeClient.bind(this) - walleeClient.checkApiServiceCompatibility() + override fun onDestroy() { + super.onDestroy() + + try { + walleeClient.unbind(this) + } catch (ex: Exception) { + println("unbinding from wallee service throw error: ${ex.message}") + } } } 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 @@ -17,7 +17,6 @@ import com.squareup.moshi.Json import com.wallee.android.till.sdk.data.State import com.wallee.android.till.sdk.data.TransactionCompletionResponse import com.wallee.android.till.sdk.data.TransactionResponse -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -25,8 +24,6 @@ import java.io.Closeable import java.math.BigDecimal import java.util.Optional import java.util.UUID -import java.util.concurrent.Executors -import kotlin.math.sign data class Amount( @Json(name = "value") val value: Int, @@ -58,11 +55,18 @@ interface WithdrawalOperationState { val amountStr: String val amountError: String val currency: String - val payed: Boolean + val authorizationState: AuthorizationState val transaction: TransactionResponse? val transactionCompletion: TransactionCompletionResponse? } +enum class AuthorizationState { + AUTHORIZATION_PENDING, + AUTHORIZATION_STARTED, + AUTHORIZED, + AUTHORIZATION_FAILED +} + private class MutableWithdrawalOperationState : WithdrawalOperationState { override val requestUid: String by derivedStateOf { UUID.randomUUID().toString() } override var exchangeBankIntegrationApiUrl: String by mutableStateOf("") @@ -71,7 +75,7 @@ private class MutableWithdrawalOperationState : WithdrawalOperationState { override var amountStr: String by mutableStateOf("") override var amountError: String by mutableStateOf("") override var currency: String by mutableStateOf("") - override var payed: Boolean by mutableStateOf(false) + override var authorizationState: AuthorizationState by mutableStateOf(AuthorizationState.AUTHORIZATION_PENDING) override var transaction: TransactionResponse? by mutableStateOf(null) override var transactionCompletion: TransactionCompletionResponse? by mutableStateOf(null) } @@ -80,9 +84,6 @@ class WithdrawalViewModel( vararg closeables: Closeable ) : ViewModel(*closeables) { - private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - - private val retries = 3 private val signals = Signals() private var exchangeSelected = false @@ -133,7 +134,7 @@ class WithdrawalViewModel( signals.setReady() } } - signals.exec() + signals.block() } fun validateInput(amount: String): Boolean { @@ -155,13 +156,25 @@ class WithdrawalViewModel( _uiState.value.currency = currency } + fun setAuthorizing() { + _uiState.value.authorizationState = AuthorizationState.AUTHORIZATION_STARTED + } + fun updateWalleeTransactionReply(response: TransactionResponse) { + println("updating wallee transaction: $response") _uiState.value.transaction = response // walleeClient.completeTransaction( // TransactionCompletion.Builder(response.transaction.lineItems) // .setReserveReference(response.reserveReference!!) // .build()) + _uiState.value.authorizationState = + if (response.state.name == "SUCCESSFUL") + AuthorizationState.AUTHORIZED + else + AuthorizationState.AUTHORIZATION_FAILED + + println("authorization state: ${_uiState.value.authorizationState}") } fun updateWalleeTransactionCompletion( @@ -183,37 +196,22 @@ class WithdrawalViewModel( fun startAuthorizationWhenReadyOrAbort( onSuccess: () -> Unit, onFailure: () -> Unit - ) = dispatcher.executor.execute { - + ) = viewModelScope.launch { + println("long-polling for parameter selection") signals.doOnReady(onSuccess) signals.doOnError(onFailure) - - dispatcher.executor.execute { - var retries = 0 // be nice... :) - var success = false - while (retries < this@WithdrawalViewModel.retries) { - terminalClient!!.retrieveWithdrawalStatus(uiState.value.encodedWopid, 10000) { - if (it.isPresent) { - signals.setReady() - success = true - } else { - retries++ - } - } - } - if (!success) { - signals.setError() - } - } - - signals.exec() + println("registered callbacks for parameter selection") + println("executing parameter selection long-polling request") + recursiveRetries(uiState.value.encodedWopid) + println("awaiting response for parameter selection") + signals.block() } private fun confirmationRequest(activity: Activity) { viewModelScope.launch { terminalClient!!.sendConfirmationRequest( _uiState.value.encodedWopid, - TerminalWithdrawalConfirmationRequest("", Amount(0, 0)) + TerminalWithdrawalConfirmationRequest(_uiState.value.encodedWopid, Amount(0, 0)) ) { if (!it) { withdrawalOperationFailed() @@ -223,6 +221,18 @@ class WithdrawalViewModel( } } + private fun recursiveRetries(wopid: String, retries: Int = 0, max: Int = 3) { + terminalClient!!.retrieveWithdrawalStatus(wopid, 10000) { + if (it.isPresent) { + signals.setReady() + } else { + if (retries < max) { + recursiveRetries(wopid, retries+1, max) + } + } + } + } + fun withdrawalOperationFailed() { viewModelScope.launch { terminalClient!!.abortWithdrawal(uiState.value.encodedWopid) {} @@ -295,13 +305,14 @@ class WithdrawalViewModel( ready = true } - fun exec() { + fun block() { if (error) { errors.forEach { it() } reset() return } while (!ready) { + println("waiting for ready") Thread.sleep(5L) } if (error) {