commit aacaaf2f49f2901e23bb2bfae03ac3fcad146bbf parent 24a60ce054930f9070f757413b1b06cec5bc5636 Author: Joel-Haeberli <haebu@rubigen.ch> Date: Wed, 8 May 2024 15:02:58 +0200 app: requests Diffstat:
16 files changed, 442 insertions(+), 81 deletions(-)
diff --git a/c2ec/api-bank-integration.go b/c2ec/api-bank-integration.go @@ -184,9 +184,11 @@ func handleWithdrawalStatus(res http.ResponseWriter, req *http.Request) { return } + var timeoutCtx context.Context + notifications := make(chan *Notification) + withdrawalAlreadyChanged := make(chan *Withdrawal) if shouldStartLongPoll { - withdrawalAlreadyChanged := make(chan *Withdrawal) go func() { // when the current state differs from the old_state // of the request, return immediately. This goroutine @@ -202,13 +204,13 @@ func handleWithdrawalStatus(res http.ResponseWriter, req *http.Request) { } }() - timeoutCtx, cancelFunc := context.WithTimeout( + var cancelFunc context.CancelFunc + timeoutCtx, cancelFunc = context.WithTimeout( req.Context(), time.Duration(longPollMilli)*time.Millisecond, ) defer cancelFunc() - notifications := make(chan *Notification) channel := "w_" + base64.StdEncoding.EncodeToString(wpd) listenFunc, err := DB.NewListener( @@ -237,9 +239,9 @@ func handleWithdrawalStatus(res http.ResponseWriter, req *http.Request) { return } } + } else { + writeWithdrawalOrError(wpd, res) } - - writeWithdrawalOrError(wpd, res) } func handleWithdrawalAbort(res http.ResponseWriter, req *http.Request) { diff --git a/wallee-c2ec/app/src/main/AndroidManifest.xml b/wallee-c2ec/app/src/main/AndroidManifest.xml @@ -33,6 +33,13 @@ android:label="@string/app_name" android:theme="@style/Theme.Walleec2ec"> </activity> + + <activity + android:name=".withdrawal.ManageActivity" + android:exported="true" + android:label="@string/app_name" + android:theme="@style/Theme.Walleec2ec"> + </activity> </application> </manifest> \ No newline at end of file diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/MainActivity.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/MainActivity.kt @@ -15,6 +15,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import ch.bfh.habej2.wallee_c2ec.ui.theme.Walleec2ecTheme +import ch.bfh.habej2.wallee_c2ec.withdrawal.ManageActivity +import ch.bfh.habej2.wallee_c2ec.withdrawal.TestTransactionScreen import ch.bfh.habej2.wallee_c2ec.withdrawal.WithdrawalActivity class MainActivity : ComponentActivity() { @@ -36,6 +38,10 @@ class MainActivity : ComponentActivity() { Button(onClick = { ctx.startActivity(Intent(this@MainActivity, WithdrawalActivity::class.java)) }) { Text(text = "Start Withdrawal") } + + Button(onClick = { ctx.startActivity(Intent(this@MainActivity, ManageActivity::class.java)) }) { + Text(text = "Manage") + } } } } diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClient.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClient.kt @@ -22,7 +22,7 @@ interface TerminalClient { fun retrieveWithdrawalStatus( wopid: String, longPollMs: Int, - oldState: WithdrawalOperationStatus = WithdrawalOperationStatus.PENDING, + oldState: WithdrawalOperationStatus = WithdrawalOperationStatus.pending, callback: (Optional<BankWithdrawalOperationStatus>) -> Unit ) 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 @@ -29,7 +29,7 @@ class TerminalClientImplementation ( private val client: OkHttpClient = OkHttpClient.Builder() - .addInterceptor(C2ECBasicAuthInterceptor(config)) + .addInterceptor(TerminalsApiBasicAuthInterceptor(config)) .build() private fun baseUrlBuilder() = config.terminalApiBaseUrl.toHttpUrl() @@ -65,29 +65,41 @@ class TerminalClientImplementation ( override fun terminalsConfig( callback: (Optional<TerminalApiConfig>) -> Unit ) = dispatcher.executor.execute { + val url = terminalsConfigUrl() + println("requesting $url") val req = Request.Builder() .get() - .url(terminalsConfigUrl()) + .url(url) .build() - val response = client.newCall(req).execute() - println("terminal config response status: ${response.code}") - callback(parseOrEmpty(response)) + client.newCall(req).execute().use { + if (it.isSuccessful) { + callback(parseOrEmpty(it)) + } else { + callback(Optional.empty()) + } + } } override fun setupWithdrawal( setupReq: TerminalWithdrawalSetup, callback: (Optional<TerminalWithdrawalSetupResponse>) -> Unit ) = dispatcher.executor.execute { - + val url = setupWithdrawalUrl() + println("requesting $url") val reqBody = serializer(TerminalWithdrawalSetup::class.java) .toJson(setupReq) .toRequestBody("application/json".toMediaType()) val req = Request.Builder() .post(reqBody) - .url(setupWithdrawalUrl()) + .url(url) .build() - val response = client.newCall(req).execute() - callback(parseOrEmpty(response)) + client.newCall(req).execute().use { + if (it.isSuccessful) { + callback(parseOrEmpty(it)) + } else { + callback(Optional.empty()) + } + } } override fun retrieveWithdrawalStatus( @@ -96,17 +108,52 @@ class TerminalClientImplementation ( oldState: WithdrawalOperationStatus, callback: (Optional<BankWithdrawalOperationStatus>) -> Unit ) = dispatcher.executor.execute { + val url = withdrawalsByWopid(wopid) + .newBuilder() + .addQueryParameter("long_poll_ms", longPollMs.toString()) + .addQueryParameter("old_state", oldState.value) + .build() + println("requesting $url") val req = Request.Builder() .get() - .url(withdrawalsByWopid(wopid) - .newBuilder() - .addQueryParameter("long_poll_ms", longPollMs.toString()) - .addQueryParameter("old_state", oldState.value) - .build() - ) + .url(url) .build() - val response = client.newCall(req).execute() - callback(parseOrEmpty(response)) + 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(Optional.empty()) + } + } + } else { + callback(parseOrEmpty(it)) + } + } else { + callback(Optional.empty()) + } + } } override fun sendConfirmationRequest( @@ -114,21 +161,20 @@ class TerminalClientImplementation ( confirmationRequest: TerminalWithdrawalConfirmationRequest, callback: (Boolean) -> Unit ) = dispatcher.executor.execute { + val url = withdrawalsConfirm(encodedWopid) + println("requesting $url") val reqBody = serializer(TerminalWithdrawalConfirmationRequest::class.java) .toJson(confirmationRequest) .toRequestBody("application/json".toMediaType()) val req = Request.Builder() .post(reqBody) - .url(withdrawalsConfirm(encodedWopid)) + .url(url) .build() - try { - val response = client.newCall(req).execute() - if (response.code != 204) { - abortWithdrawal(encodedWopid) {} + client.newCall(req).execute().use { + if (it.code != 204) { + callback(false) } callback(true) - } catch (ex: IOException) { - callback(false) } } @@ -136,15 +182,18 @@ class TerminalClientImplementation ( encodedWopid: String, callback: (Boolean) -> Unit ) = dispatcher.executor.execute { + val url = withdrawalsAbort(encodedWopid) + println("requesting $url") val req = Request.Builder() .delete() .url(withdrawalsAbort(encodedWopid)) .build() - try { - client.newCall(req).execute() - callback(true) - } catch (ex: IOException) { - callback(false) + client.newCall(req).execute().use { + if (it.isSuccessful) { + callback(true) + } else { + callback(false) + } } } @@ -163,7 +212,7 @@ class TerminalClientImplementation ( return Optional.empty() } - private class C2ECBasicAuthInterceptor( + private class TerminalsApiBasicAuthInterceptor( private val config: TalerTerminalConfig ) : Interceptor { @@ -174,7 +223,6 @@ class TerminalClientImplementation ( .encodeToString("${config.username}:${config.accessToken}".toByteArray()) val authorizationHeaderValue = "Basic $base64EncodedCredentials" - println("auth header: $authorizationHeaderValue") return chain.proceed( chain.request().newBuilder() .header("Authorization", authorizationHeaderValue) diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt @@ -47,7 +47,7 @@ class TerminalClientMock: TerminalClient { callback.invoke( Optional.of( BankWithdrawalOperationStatus( - WithdrawalOperationStatus.SELECTED, + WithdrawalOperationStatus.selected, Amount(10,0), // Amount(10,0), // Amount(0,0), @@ -82,7 +82,7 @@ class TerminalClientMock: TerminalClient { println("aborting withdrawal") } - fun mockWopidOrReservePubKey(): String { + private fun mockWopidOrReservePubKey(): String { val wopid = ByteArray(32) SecureRandom().nextBytes(wopid) diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/model/TerminalsApiModel.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/model/TerminalsApiModel.kt @@ -32,10 +32,10 @@ data class TerminalWithdrawalConfirmationRequest( enum class WithdrawalOperationStatus(val value: String) { - PENDING("pending"), - SELECTED("selected"), - CONFIRMED("confirmed"), - ABORTED("aborted") + pending("pending"), + selected("selected"), + confirmed("confirmed"), + aborted("aborted") } data class BankWithdrawalOperationStatus( 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 @@ -1,13 +1,15 @@ package ch.bfh.habej2.wallee_c2ec.client.wallee -import ch.bfh.habej2.wallee_c2ec.withdrawal.WithdrawalActivity +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 import com.wallee.android.till.sdk.data.TransactionResponse class WalleeResponseHandler( - private val activity: WithdrawalActivity, + private val activity: Activity, private val model: WithdrawalViewModel ) : ResponseHandler() { @@ -21,7 +23,24 @@ class WalleeResponseHandler( return } - println("C2EC-TRANSACTION-RESPONSE: ${response}") + println("C2EC-TRANSACTION-RESPONSE: $response") + + if (response.transaction.metaData.isNotEmpty()) { + response.transaction.metaData.entries.forEach { + println("METADATA: ${it.key}=${it.value}") + } + } else { + println("METADATA EMPTY") + } + + println("TRANSACTION: ${response.transaction.totalAmountIncludingTax} ${response.transaction.currency}") + response.transaction.lineItems.forEach { + println("TRANSACTION: LINE-ITEM id=${it.id}, name=${it.name}, amount-including-taxes=${it.totalAmountIncludingTax}") + } + println("TRANSACTION: ${response.state}") + println("TRANSACTION: ${response.authorizationCode}") + println("TRANSACTION: ${response.acquirerId}") + println("TRANSACTION: ${response.reserveReference}") model.updateWalleeTransactionReply(response) } @@ -41,11 +60,21 @@ class WalleeResponseHandler( model.updateWalleeTransactionCompletion(response, activity) } + override fun executeFinalBalanceReply(result: FinalBalanceResult?) { + + if (result == null) { + println("executed final balance but got no result") + } else { + println("executed final balance: ${result.resultCode.code} ${result.resultCode.description}") + } + } + override fun checkApiServiceCompatibilityReply( isCompatible: Boolean?, apiServiceVersion: String? ) { + println("terminal app sdk compatible: $isCompatible") if (isCompatible == null || !isCompatible) { // just dont start withdrawals when api is not compatible activity.finish() 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 @@ -57,8 +57,7 @@ fun AmountScreen(model: WithdrawalViewModel, activity: Activity, navigateToWhenA Button(onClick = { println("clicked 'pay'") - model.setupWithdrawal(activity) - navigateToWhenAmountEntered() + model.setupWithdrawal(activity, navigateToWhenAmountEntered) }, enabled = model.validAmount(uiState.amountStr)) { Text(text = "withdraw") } 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 @@ -0,0 +1,67 @@ +package ch.bfh.habej2.wallee_c2ec.withdrawal + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +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.wallee.android.till.sdk.ApiClient + +class ManageActivity : ComponentActivity() { + + private lateinit var walleeClient: ApiClient + + private var unbound = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + Walleec2ecTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button(onClick = { + walleeClient.executeFinalBalance() + }) { + Text(text = "Execute Final Balance") + } + + TestTransactionScreen(activity = this@ManageActivity) + + Button(onClick = { + onDestroy() + this@ManageActivity.finish() + }) { + Text(text = "back") + } + } + } + } + } + + walleeClient = ApiClient(WalleeResponseHandler(this, WithdrawalViewModel())) + walleeClient.bind(this) + walleeClient.checkApiServiceCompatibility() + } + + override fun onDestroy() { + super.onDestroy() + if (!unbound) { + walleeClient.unbind(this) + unbound = true + } + } +} +\ No newline at end of file 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 @@ -1,5 +1,6 @@ package ch.bfh.habej2.wallee_c2ec.withdrawal +import android.annotation.SuppressLint import android.app.Activity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -18,6 +19,7 @@ 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 fun RegisterParametersScreen( model: WithdrawalViewModel, @@ -38,18 +40,28 @@ fun RegisterParametersScreen( Text(text = "register the withdrawal parameters") // Text(text = "QR-Code content: ${TalerConstants.formatTalerUri(uiState.exchangeBankIntegrationApiUrl, uiState.encodedWopid)}") - QRCode(TalerConstants.formatTalerUri(uiState.exchangeBankIntegrationApiUrl, uiState.encodedWopid)) + QRCode( + TalerConstants.formatTalerUri( + uiState.exchangeBankIntegrationApiUrl, + uiState.encodedWopid + ) + ) - Button(onClick = { - model.withdrawalOperationFailed() + AbortButton(model = model, activity = activity) + + model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { activity.finish() - }) { - Text(text = "abort") } } +} - model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) { +@Composable +fun AbortButton(model: WithdrawalViewModel, activity: Activity) { + Button(onClick = { + model.withdrawalOperationFailed() activity.finish() + }) { + Text(text = "abort") } } 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 @@ -0,0 +1,52 @@ +package ch.bfh.habej2.wallee_c2ec.withdrawal + +import android.app.Activity +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import ch.bfh.habej2.wallee_c2ec.client.wallee.WalleeResponseHandler +import com.wallee.android.till.sdk.ApiClient +import com.wallee.android.till.sdk.data.LineItem +import com.wallee.android.till.sdk.data.Transaction +import com.wallee.android.till.sdk.data.TransactionProcessingBehavior +import java.math.BigDecimal +import java.util.Currency +import java.util.UUID + +@Composable +fun TestTransactionScreen(activity: Activity) { + + val walleeClient = ApiClient(WalleeResponseHandler(activity, WithdrawalViewModel())) + walleeClient.bind(activity) + walleeClient.checkApiServiceCompatibility() + + val lineitemuuid = UUID.randomUUID().toString() + val merchRef = UUID.randomUUID().toString() + val invRef = UUID.randomUUID().toString() + + val withdrawalAmount = LineItem + .ListBuilder(lineitemuuid, BigDecimal(10.50)) + .build() + + val transaction = Transaction.Builder(withdrawalAmount) + .setCurrency(Currency.getInstance("CHF")) + .setInvoiceReference(invRef) + .setMerchantReference(merchRef) + .setTransactionProcessingBehavior(TransactionProcessingBehavior.COMPLETE_IMMEDIATELY) + .build() + + Column { + Button(onClick = { + try { + walleeClient.authorizeTransaction(transaction) + //walleeClient.unbind(activity) + } catch (e: Exception) { + println("FAILED authorizing transaction ${e.message}") + e.printStackTrace() + } + }) { + Text(text = "Authorize test transaction") + } + } +} +\ No newline at end of file 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 @@ -48,10 +48,4 @@ class WithdrawalActivity : ComponentActivity() { walleeClient.bind(this) walleeClient.checkApiServiceCompatibility() } - - override fun onDestroy() { - super.onDestroy() - walleeClient.unbind(this) - } - } 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,6 +17,7 @@ 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 @@ -24,6 +25,8 @@ 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, @@ -47,7 +50,7 @@ object TalerConstants { } @Stable -interface WithdrawalOperationState{ +interface WithdrawalOperationState { val requestUid: String val exchangeBankIntegrationApiUrl: String val encodedWopid: String @@ -60,11 +63,11 @@ interface WithdrawalOperationState{ val transactionCompletion: TransactionCompletionResponse? } -private class MutableWithdrawalOperationState: WithdrawalOperationState { - override val requestUid: String by derivedStateOf{UUID.randomUUID().toString()} +private class MutableWithdrawalOperationState : WithdrawalOperationState { + override val requestUid: String by derivedStateOf { UUID.randomUUID().toString() } override var exchangeBankIntegrationApiUrl: String by mutableStateOf("") override var encodedWopid: String by mutableStateOf("") - override var amount: Amount by mutableStateOf(Amount(0,0)) + override var amount: Amount by mutableStateOf(Amount(0, 0)) override var amountStr: String by mutableStateOf("") override var amountError: String by mutableStateOf("") override var currency: String by mutableStateOf("") @@ -77,7 +80,10 @@ 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 private var terminalClient: TerminalClient? = null @@ -108,22 +114,26 @@ class WithdrawalViewModel( exchangeSelected = true } - fun setupWithdrawal(activity: Activity) { + fun setupWithdrawal(activity: Activity, navigateToWhenAmountEntered: () -> Unit) { val setupReq = TerminalWithdrawalSetup( _uiState.value.requestUid, _uiState.value.amount ) + signals.doOnReady(navigateToWhenAmountEntered) viewModelScope.launch { terminalClient!!.setupWithdrawal(setupReq) { if (!it.isPresent) { activity.finish() } - println("retrieved WOPID from c2ec: ${it.get().withdrawalId}") - _uiState.value.encodedWopid = it.get().withdrawalId + val wopid = it.get().withdrawalId + println("retrieved WOPID from c2ec: $wopid") + _uiState.value.encodedWopid = wopid + signals.setReady() } } + signals.exec() } fun validateInput(amount: String): Boolean { @@ -145,12 +155,19 @@ class WithdrawalViewModel( _uiState.value.currency = currency } - fun updateWalleeTransactionReply(transaction: TransactionResponse) { + fun updateWalleeTransactionReply(response: TransactionResponse) { - _uiState.value.transaction = transaction + _uiState.value.transaction = response +// walleeClient.completeTransaction( +// TransactionCompletion.Builder(response.transaction.lineItems) +// .setReserveReference(response.reserveReference!!) +// .build()) } - fun updateWalleeTransactionCompletion(completion: TransactionCompletionResponse, activity: Activity) { + fun updateWalleeTransactionCompletion( + completion: TransactionCompletionResponse, + activity: Activity + ) { _uiState.value.transactionCompletion = completion if (completion.state == State.FAILED) { @@ -166,30 +183,37 @@ class WithdrawalViewModel( fun startAuthorizationWhenReadyOrAbort( onSuccess: () -> Unit, onFailure: () -> Unit - ) { - var retries = 0 - var success = false - viewModelScope.launch { + ) = dispatcher.executor.execute { + + 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) { - onSuccess() + signals.setReady() success = true - retries = this@WithdrawalViewModel.retries + 1 + } else { + retries++ } } } - if (!success) {withdrawalOperationFailed() - onFailure() + if (!success) { + signals.setError() } } + + signals.exec() } private fun confirmationRequest(activity: Activity) { viewModelScope.launch { terminalClient!!.sendConfirmationRequest( _uiState.value.encodedWopid, - TerminalWithdrawalConfirmationRequest("", Amount(0,0)) + TerminalWithdrawalConfirmationRequest("", Amount(0, 0)) ) { if (!it) { withdrawalOperationFailed() @@ -205,7 +229,7 @@ class WithdrawalViewModel( } } - fun validAmount(inp: String) = Regex( "\\d+(\\.\\d+)?").matches(inp) + fun validAmount(inp: String) = Regex("\\d+(\\.\\d+)?").matches(inp) /** * Format expected X[.X], X an integer @@ -247,6 +271,55 @@ class WithdrawalViewModel( _uiState.value.amountStr = "" } + class Signals { + + private var listeners = mutableListOf<() -> Unit>() + private var errors = mutableListOf<() -> Unit>() + private var ready = false + private var error = false + + fun doOnReady(f: () -> Unit) { + listeners.add(f) + } + + fun doOnError(e: () -> Unit) { + errors.add(e) + } + + fun setError() { + error = true + ready = true + } + + fun setReady() { + ready = true + } + + fun exec() { + if (error) { + errors.forEach { it() } + reset() + return + } + while (!ready) { + Thread.sleep(5L) + } + if (error) { + errors.forEach { it() } + } else { + listeners.forEach { it() } + } + reset() + } + + private fun reset() { + listeners = mutableListOf() + errors = mutableListOf() + error = false + ready = false + } + } + // API's FOR TESTING / PREVIEW // fun mockExchange() { // val cfg = TalerTerminalConfig( 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 @@ -0,0 +1,69 @@ +package ch.bfh.habej2.wallee_c2ec + +import ch.bfh.habej2.wallee_c2ec.client.taler.TerminalClientImplementation +import ch.bfh.habej2.wallee_c2ec.client.taler.config.TalerTerminalConfig +import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtils.encodeCrock +import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtilsTest +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.Amount +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.OrderWith +import org.junit.runner.manipulation.Alphanumeric + +class TerminalApiClientIntegrationTest { + + private val config = TalerTerminalConfig( + "TEST Exchange", + "http://taler-c2ec.ti.bfh.ch", + "Simulation-1", + "Gofobz8XYEX0H3rts4+w3K7bU68wSP2N5Y36FBe4/f8=" + ) + + private val client = TerminalClientImplementation(config) + + private var currency = "" + private var wopid = "" + + private val sleepMs = 1000L + private val longPollMs = 3000L + + @Test + fun flow() { + // load config + client.terminalsConfig { + assertTrue(it.isPresent) + currency = it.get().currency + println("loaded config. currency=$currency") + } + + Thread.sleep(sleepMs) + + // setup withdrawal + client.setupWithdrawal(TerminalWithdrawalSetup( + encodeCrock(CyptoUtilsTest().rand32Bytes()), + Amount(21, 30, currency) + )) { + assertTrue(it.isPresent) + wopid = it.get().withdrawalId + println("setup withdrawal. wopid=$wopid") + } + Thread.sleep(sleepMs) + + // send check request + client.sendConfirmationRequest(wopid, TerminalWithdrawalConfirmationRequest("TEST-$wopid", Amount(21, 30, currency))) { + assertTrue(it) + println("payment is attested. result=$it") + } + + // load status + client.retrieveWithdrawalStatus(wopid, longPollMs.toInt()) { + assertTrue(it.isPresent) + println("status retrieved. status=${it.get().status}") + } + + Thread.sleep(longPollMs+1000) + } +} +\ No newline at end of file diff --git a/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CyptoUtilsTest.kt b/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CyptoUtilsTest.kt @@ -45,7 +45,7 @@ class CyptoUtilsTest { assert(decoded1.contentEquals(decoded2)) } - private fun rand32Bytes(): ByteArray { + fun rand32Bytes(): ByteArray { val wopid = ByteArray(32) val rand = SecureRandom() rand.nextBytes(wopid) // will seed automatically