commit 529c0875f9bfe6bd8e338a519e1737dbdd52c413 parent ef20d49e0a5eda8bb782532d7a5fdc4099618fd4 Author: Joel-Haeberli <haebu@rubigen.ch> Date: Sat, 25 May 2024 10:30:48 +0200 app: fix authorize Diffstat:
10 files changed, 122 insertions(+), 108 deletions(-)
diff --git a/wallee-c2ec/.idea/deploymentTargetSelector.xml b/wallee-c2ec/.idea/deploymentTargetSelector.xml @@ -5,9 +5,6 @@ <SelectionState runConfigName="app"> <option name="selectionMode" value="DROPDOWN" /> </SelectionState> - <SelectionState runConfigName="AuthorizePaymentScreenPreview"> - <option name="selectionMode" value="DROPDOWN" /> - </SelectionState> </selectionStates> </component> </project> \ No newline at end of file diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientImplementation.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientImplementation.kt @@ -125,41 +125,46 @@ class TerminalClientImplementation ( .get() .url(url) .build() - client.newCall(req).execute().use { - if (it.isSuccessful) { - if (it.code == 204) { - // in case when old_state == current state (for the terminal this doesn't matter - val url2 = withdrawalsByWopid(wopid) - .newBuilder() - .build() - println("requesting $url2") - val req2 = Request.Builder() - .get() - .url(url2) - .build() - client.newCall(req2).execute().use { res2 -> - if (res2.isSuccessful) { - val opt = parseOrEmpty<BankWithdrawalOperationStatus>(res2) - if (opt.isPresent) { - if (opt.get().status == WithdrawalOperationStatus.pending) { - callback(Optional.empty()) + try { + client.newCall(req).execute().use { + if (it.isSuccessful) { + if (it.code == 204) { + // in case when old_state == current state (for the terminal this doesn't matter + val url2 = withdrawalsByWopid(wopid) + .newBuilder() + .build() + println("requesting $url2") + val req2 = Request.Builder() + .get() + .url(url2) + .build() + client.newCall(req2).execute().use { res2 -> + if (res2.isSuccessful) { + val opt = parseOrEmpty<BankWithdrawalOperationStatus>(res2) + if (opt.isPresent) { + if (opt.get().status == WithdrawalOperationStatus.pending) { + callback(Optional.empty()) + } else { + callback(opt) + } } else { callback(opt) } + callback(parseOrEmpty(res2)) } else { - callback(opt) + callback(Optional.empty()) } - callback(parseOrEmpty(res2)) - } else { - callback(Optional.empty()) } + } else { + callback(parseOrEmpty(it)) } } else { - callback(parseOrEmpty(it)) + callback(Optional.empty()) } - } else { - callback(Optional.empty()) } + } catch (ex: Exception) { + println("withdrawal status request failed: ${ex.message}") + callback(Optional.empty()) } } 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 @@ -18,7 +18,7 @@ class WalleeResponseHandler( println("C2EC-TRANSACTION-RESPONSE: $response") if (response == null) { - model.withdrawalOperationFailed() + model.withdrawalOperationFailed(activity) activity.finish() return } @@ -42,7 +42,7 @@ class WalleeResponseHandler( println("TRANSACTION: ${response.acquirerId}") println("TRANSACTION: ${response.reserveReference}") - model.updateWalleeTransactionReply(response) + model.updateWalleeTransactionReply(response, activity) } override fun completeTransactionReply(response: TransactionCompletionResponse?) { @@ -50,7 +50,7 @@ class WalleeResponseHandler( println("C2EC-COMPLETION-RESPONSE: $response") if (response == null) { - model.withdrawalOperationFailed() + model.withdrawalOperationFailed(activity) activity.finish() return } diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AmountScreen.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AmountScreen.kt @@ -25,7 +25,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @Composable -fun AmountScreen(model: WithdrawalViewModel, activity: Activity, navigateToWhenAmountEntered: () -> Unit) { +fun AmountScreen( + model: WithdrawalViewModel, + activity: Activity, + navigateToWhenAmountEntered: () -> Unit +) { val uiState by model.uiState.collectAsState() val configuration = LocalConfiguration.current @@ -53,8 +57,6 @@ fun AmountScreen(model: WithdrawalViewModel, activity: Activity, navigateToWhenA ) ) - //Text(text = uiState.amountStr) - Button(onClick = { println("clicked 'pay'") model.setupWithdrawal(activity, navigateToWhenAmountEntered) @@ -63,8 +65,7 @@ fun AmountScreen(model: WithdrawalViewModel, activity: Activity, navigateToWhenA } Button(onClick = { - model.withdrawalOperationFailed() - activity.finish() + model.withdrawalOperationFailed(activity) }) { Text(text = "abort") } 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 @@ -53,14 +53,12 @@ fun AuthorizePaymentScreen(model: WithdrawalViewModel, activity: Activity, clien client.authorizeTransaction(transaction) } catch (e: Exception) { println("FAILED authorizing transaction ${e.message}") - model.withdrawalOperationFailed() - activity.finish() + model.withdrawalOperationFailed(activity) e.printStackTrace() } Button(onClick = { - model.withdrawalOperationFailed() - activity.finish() + model.withdrawalOperationFailed(activity) }) { Text(text = "abort") } 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 @@ -42,7 +42,8 @@ class ManageActivity : ComponentActivity() { // this is only for testing and simulates a wallet registering parameters fun simulateParameterRegistration( exchangeBankIntegrationApiUrl: String, - wopid: String + wopid: String, + ) { println("simulating wallet parameter selection") // we will just use the wopid as reserve pub key... 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 @@ -8,19 +8,12 @@ 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 import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import kotlinx.coroutines.launch -import java.util.concurrent.Executors -import kotlin.coroutines.coroutineContext @SuppressLint("StateFlowValueCalledInComposition") @Composable @@ -33,14 +26,6 @@ fun RegisterParametersScreen( val uiState by model.uiState.collectAsState() val configuration = LocalConfiguration.current - if (!ManageActivity.SIM_WALLET_ENABLED) { - LaunchedEffect(Unit) { - model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { - activity.finish() - } - } - } - Column( Modifier.width(configuration.screenWidthDp.dp), verticalArrangement = Arrangement.Center, @@ -51,29 +36,27 @@ 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)}") + println("QR-CODE: ${TalerConstants.formatTalerUri(uiState.terminalsApiBasePath, uiState.encodedWopid)}") QRCode( TalerConstants.formatTalerUri( - uiState.exchangeBankIntegrationApiUrl, + uiState.terminalsApiBasePath, uiState.encodedWopid ) ) AbortButton(model = model, activity = activity) - - if (ManageActivity.SIM_WALLET_ENABLED) { - Button(onClick = { + Button(onClick = { + if (ManageActivity.SIM_WALLET_ENABLED) { ManageActivity.simulateParameterRegistration( - uiState.exchangeBankIntegrationApiUrl, + uiState.terminalsApiBasePath, uiState.encodedWopid ) - - model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { - activity.finish() - } - }) { - Text(text = "simulate registration") } + model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { + activity.finish() + } + }) { + Text(text = "authorize") } } } @@ -81,8 +64,7 @@ fun RegisterParametersScreen( @Composable fun AbortButton(model: WithdrawalViewModel, activity: Activity) { Button(onClick = { - model.withdrawalOperationFailed() - activity.finish() + model.withdrawalOperationFailed(activity) }) { Text(text = "abort") } 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 @@ -5,6 +5,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material3.Button import androidx.compose.material3.Text @@ -24,14 +25,14 @@ data class Summary( var success: Boolean = true ) -class SummaryActivity: ComponentActivity() { +class SummaryActivity : ComponentActivity() { companion object { var summary: Summary = reset() private fun reset(): Summary = Summary( - Amount(0,0), - Amount(0,0), + Amount(0, 0), + Amount(0, 0), "", "" ) @@ -42,9 +43,9 @@ class SummaryActivity: ComponentActivity() { setContent { - Column ( - horizontalAlignment = Alignment.CenterHorizontally - ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally) { if (summary.success) { Text( @@ -58,8 +59,7 @@ class SummaryActivity: ComponentActivity() { Text(text = "Withdrawal Operation ID (QR Code):") // QRCode(qrCodeContent = summary.encodedWopid, 2.dp) } else { - - Text(text = "Failed processing transaction.") + Text(text = "Failed authorizing payment.") Text(text = "Withdrawal aborted.") } 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 @@ -15,24 +15,28 @@ import ch.bfh.habej2.wallee_c2ec.client.taler.config.TalerTerminalConfig import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtils import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalConfirmationRequest import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalSetup +import ch.bfh.habej2.wallee_c2ec.client.taler.model.WithdrawalOperationStatus import ch.bfh.habej2.wallee_c2ec.withdrawal.WithdrawalViewModel.Companion.generateTransactionIdentifier +import com.google.gson.annotations.Until import com.squareup.moshi.Json import com.wallee.android.till.sdk.ApiClient import com.wallee.android.till.sdk.data.State import com.wallee.android.till.sdk.data.TransactionCompletion import com.wallee.android.till.sdk.data.TransactionCompletionResponse import com.wallee.android.till.sdk.data.TransactionResponse +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.asExecutor import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.newSingleThreadContext import java.io.Closeable import java.math.BigDecimal import java.security.SecureRandom 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, @@ -51,14 +55,14 @@ object TalerConstants { const val TALER_INTEGRATION = "/taler-integration" const val TALER_INTEGRATION_WITHDRAWAL_OPERATION = "/withdrawal-operation" - fun formatTalerUri(bankIntegrationApiPath: String, encodedWopid: String) = - "taler://withdraw/$bankIntegrationApiPath$TALER_INTEGRATION_WITHDRAWAL_OPERATION/$encodedWopid" + fun formatTalerUri(terminalsApiBasePath: String, encodedWopid: String) = + "taler://withdraw/$terminalsApiBasePath$TALER_INTEGRATION_WITHDRAWAL_OPERATION/$encodedWopid" } @Stable interface WithdrawalOperationState { val requestUid: String - val exchangeBankIntegrationApiUrl: String + val terminalsApiBasePath: String val encodedWopid: String val amount: Amount val amountStr: String @@ -82,7 +86,7 @@ enum class TransactionState { private class MutableWithdrawalOperationState : WithdrawalOperationState { override val requestUid: String by derivedStateOf { UUID.randomUUID().toString() } - override var exchangeBankIntegrationApiUrl: String by mutableStateOf("") + override var terminalsApiBasePath: String by mutableStateOf("") override var encodedWopid: String by mutableStateOf("") override var amount: Amount by mutableStateOf(Amount(0, 0)) override var amountStr: String by mutableStateOf("") @@ -130,7 +134,7 @@ class WithdrawalViewModel( if (!it.isPresent) { activity.finish() } else { - _uiState.value.exchangeBankIntegrationApiUrl = + _uiState.value.terminalsApiBasePath = "${cfg.terminalApiBaseUrl}${TalerConstants.TALER_INTEGRATION}" this@WithdrawalViewModel.updateCurrency(it.get().currency) } @@ -138,7 +142,10 @@ class WithdrawalViewModel( exchangeSelected = true } - fun setupWithdrawal(activity: Activity, navigateToWhenAmountEntered: () -> Unit) { + fun setupWithdrawal( + activity: Activity, + navigateToWhenAmountEntered: () -> Unit + ) { val setupReq = TerminalWithdrawalSetup( _uiState.value.requestUid, @@ -149,7 +156,7 @@ class WithdrawalViewModel( viewModelScope.launch { terminalClient!!.setupWithdrawal(setupReq) { if (!it.isPresent) { - activity.finish() + withdrawalOperationFailed(activity) } val wopid = it.get().withdrawalId println("retrieved WOPID from c2ec: $wopid") @@ -183,7 +190,7 @@ class WithdrawalViewModel( _uiState.value.transactionState = TransactionState.AUTHORIZATION_STARTED } - fun updateWalleeTransactionReply(response: TransactionResponse) { + fun updateWalleeTransactionReply(response: TransactionResponse, activity: Activity) { println("updating wallee transaction: $response") _uiState.value.transaction = response @@ -196,7 +203,7 @@ class WithdrawalViewModel( println("authorization state: ${_uiState.value.transactionState}") if (_uiState.value.transactionState == TransactionState.AUTHORIZATION_FAILED) { println("authorization failed... aborting withdrawal") - withdrawalOperationFailed() + withdrawalOperationFailed(activity) return } @@ -218,7 +225,7 @@ class WithdrawalViewModel( if (completion.state == State.FAILED) { println("completion of the transaction failed... aborting") _uiState.value.transactionState = TransactionState.COMPLETION_FAILED - withdrawalOperationFailed() + withdrawalOperationFailed(activity) return } @@ -233,18 +240,23 @@ class WithdrawalViewModel( onFailure: () -> Unit ) = viewModelScope.launch { println("long-polling for parameter selection") - signals.doOnReady(onSuccess) - signals.doOnError(onFailure) + val sigs = Signals() + sigs.doOnReady(onSuccess) + sigs.doOnError(onFailure) println("registered callbacks for parameter selection") println("executing parameter selection long-polling request") try { - recursiveRetries(uiState.value.encodedWopid) + recursiveRetries(uiState.value.encodedWopid, sigs) } catch (ex: Exception) { println("error occured: $ex") - signals.setError() + sigs.setError() } println("awaiting response for parameter selection") - signals.block() + sigs.block() + } + + fun navToAuthHook(navigateToWhenRegistered: () -> Unit) { + navigateToWhenRegistered() } private fun confirmationRequest(activity: Activity) { @@ -257,8 +269,7 @@ class WithdrawalViewModel( ) ) { if (!it) { - withdrawalOperationFailed() - activity.finish() + withdrawalOperationFailed(activity) return@sendConfirmationRequest } SummaryActivity.summary = @@ -274,28 +285,35 @@ class WithdrawalViewModel( } } - private fun recursiveRetries(wopid: String, retries: Int = 0, max: Int = 3) { + private fun recursiveRetries(wopid: String, sigs: Signals, retries: Int = 0, max: Int = 3) { terminalClient!!.retrieveWithdrawalStatus(wopid, 10000) { if (it.isPresent) { - signals.setReady() + sigs.setReady() } else { if (retries < max) { - recursiveRetries(wopid, retries+1, max) + recursiveRetries(wopid, sigs, retries+1, max) } } } } - fun withdrawalOperationFailed() { + fun withdrawalOperationFailed(activity: Activity) { viewModelScope.launch { if (_uiState.value.transactionState == TransactionState.AUTHORIZATION_PENDING || _uiState.value.transactionState == TransactionState.AUTHORIZATION_FAILED || _uiState.value.transactionState == TransactionState.COMPLETION_FAILED) { - terminalClient!!.abortWithdrawal(uiState.value.encodedWopid) {} -// SummaryActivity.summary.success = false -// val activity = WithdrawalActivity() -// val intent = Intent(activity, SummaryActivity::class.java) -// activity.startActivity(intent) + terminalClient!!.abortWithdrawal(uiState.value.encodedWopid) { + SummaryActivity.summary = + Summary( + _uiState.value.amount, + Amount(0,0), + _uiState.value.currency, + _uiState.value.encodedWopid, + success = false + ) + val intent = Intent(activity, SummaryActivity::class.java) + activity.startActivity(intent) + } } } } @@ -373,7 +391,7 @@ class WithdrawalViewModel( return } while (!ready) { - println("waiting for ready") + //println("waiting for ready") Thread.sleep(5L) } if (error) { diff --git a/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/TerminalApiClientIntegrationTest.kt b/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/TerminalApiClientIntegrationTest.kt @@ -8,6 +8,7 @@ import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalConfirmati import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalSetup import ch.bfh.habej2.wallee_c2ec.client.taler.model.WithdrawalOperationStatus import ch.bfh.habej2.wallee_c2ec.withdrawal.Amount +import ch.bfh.habej2.wallee_c2ec.withdrawal.ManageActivity import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.OrderWith @@ -44,7 +45,7 @@ class TerminalApiClientIntegrationTest { // setup withdrawal client.setupWithdrawal(TerminalWithdrawalSetup( encodeCrock(CyptoUtilsTest().rand32Bytes()), - Amount(21, 30, currency) + "CHF:21.30" )) { assertTrue(it.isPresent) wopid = it.get().withdrawalId @@ -53,7 +54,7 @@ class TerminalApiClientIntegrationTest { Thread.sleep(sleepMs) // send check request - client.sendConfirmationRequest(wopid, TerminalWithdrawalConfirmationRequest("TEST-$wopid", Amount(21, 30, currency))) { + client.sendConfirmationRequest(wopid, TerminalWithdrawalConfirmationRequest("TEST-$wopid", "CHF:21.30")) { assertTrue(it) println("payment is attested. result=$it") } @@ -66,4 +67,15 @@ class TerminalApiClientIntegrationTest { Thread.sleep(longPollMs+1000) } + + @Test + fun simulateRegistration() { + + // ATD2GW6ZNJ5FQ8B6R890C4G13RN3RZTQ1P6VT2ZT5V04AP7DG9K0 + wopid = ""; + ManageActivity.simulateParameterRegistration( + "http://taler-c2ec.ti.bfh.ch/taler-integration", + wopid + ) + } } \ No newline at end of file