cashless2ecash

cashless2ecash: pay with cards for digital cash (experimental)
Log | Files | Refs | README

commit 62cce7fac9bc1a2dfad440ea126d6dacd5e0af1d
parent be0bcae390ac5f978db5999a367c9c6497d4f10b
Author: Joel-Haeberli <haebu@rubigen.ch>
Date:   Wed,  1 May 2024 14:14:50 +0200

installation instructions

Diffstat:
Mc2ec/install/build_app.sh | 10++++------
Mc2ec/install/installation_notes.md | 78++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Awallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClient.kt | 27+++++++++++++++++++++++++++
Mwallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientImplementation.kt | 22++++++++++++----------
Awallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mwallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AmountScreen.kt | 15++++++++-------
Mwallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalActivity.kt | 1-
Mwallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/WithdrawalViewModel.kt | 31+++++++++++++++++++++++++++----
8 files changed, 211 insertions(+), 54 deletions(-)

diff --git a/c2ec/install/build_app.sh b/c2ec/install/build_app.sh @@ -1,14 +1,15 @@ #!/bin/bash -if [ "$#" -ne 1 ]; then - echo "Usage: $0 <source-root>" +if [ "$#" -ne 2 ]; then + echo "Usage: $0 <source-root> <install-dir>" exit 1 fi REPO_ROOT=$1 +INSTALL_DIR=$2 build_c2ec() { - go build $REPO_ROOT + go build -o $INSTALL_DIR $REPO_ROOT if [ $? -ne 0 ]; then echo "Failed to build C2EC using Go" exit 1 @@ -16,6 +17,3 @@ build_c2ec() { } build_c2ec -if [ $? -ne 0 ]; then - exit 1 -fi diff --git a/c2ec/install/installation_notes.md b/c2ec/install/installation_notes.md @@ -7,6 +7,7 @@ how to install exchange and C2EC - Debian - git - postgres >= 15.6 +- go ([installing Go](https://go.dev/doc/install)) - it's a good idea to read [Exchange Operator Manual](https://docs.taler.net/taler-exchange-manual.html) first ## Required Exchange binaries @@ -22,37 +23,63 @@ To allow the withdrawal of Taler, I will need following binaries: ## Setup Commands -`sudo echo "deb [signed-by=/etc/apt/keyrings/taler-systems.gpg] https://deb.taler.net/apt/debian bookworm main" > /etc/apt/sources.list.d/taler.list` +```bash -`sudo wget -O /etc/apt/keyrings/taler-systems.gpg https://taler.net/taler-systems.gpg` +# Add taler repo and install +sudo echo "deb [signed-by=/etc/apt/keyrings/taler-systems.gpg] https://deb.taler.net/apt/debian bookworm main" > /etc/apt/sources.list.d/taler.list` +sudo wget -O /etc/apt/keyrings/taler-systems.gpg https://taler.net/taler-systems.gpg +sudo apt update +sudo apt install taler-exchange -`sudo apt update` +# install git +sudo apt install git -`sudo apt install taler-exchange` +# download Go and install +export GO_TAR=go1.22.2.linux-amd64.tar.gz +sudo wget -O $GO_TAR https://go.dev/dl/$GO_TAR +sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf $GO_TAR +echo 'export PATH=$PATH:/usr/local/go/bin' >> $HOME/.profile +source $HOME/.profile +go version +``` ## Configure +### Getting the source -### Preparing the database - -`sudo passwd postgres` - -`su postgres` - -`psql` - -SQL `CREATE DATABASE c2ec;` - -SQL `CREATE USER c2ec_admin WITH ENCRYPTED PASSWORD [..]` -> keepass - -SQL `GRANT ALL PRIVILEGES ON DATABASE c2ec TO c2ec_admin;` - -`` - -For CLI (managing terminals and providers): -SQL `CREATE USER c2ec_operator WITH ENCRYPTED PASSWORD [..]` -> keepass - +`git clone https://git.taler.net/cashless2ecash.git` +### Preparing the database -For the API (handling withdrawals): -SQL `CREATE USER c2ec_api WITH ENCRYPTED PASSWORD [..]` -> keepass -\ No newline at end of file +```bash +sudo passwd postgres # change default password of postgres user +su postgres +psql +``` + +```sql +CREATE DATABASE c2ec; +CREATE USER c2ec_admin WITH ENCRYPTED PASSWORD [..]; -- keepass +GRANT ALL PRIVILEGES ON DATABASE c2ec TO c2ec_admin; +--For CLI (managing terminals and providers): +CREATE USER c2ec_operator WITH ENCRYPTED PASSWORD [..]; -- keepass +--For the API (handling withdrawals): +CREATE USER c2ec_api WITH ENCRYPTED PASSWORD [..]; -- keepass +``` + +exit psql, change back to normal user (type 2x 'exit') + +### Setting up c2ec schmema +find src directory of cashless2ecash, find c2ec/install/setup_db.sh + +```bash +export PGHOST=localhost` +export PGPORT=5432 +./setup_db.sh c2ec_admin [PASSWORD OBFUSCATED] c2ec ./.. +``` + +### Building the app + +```bash +./build_app.sh ./.. $HOME +``` 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 @@ -0,0 +1,26 @@ +package ch.bfh.habej2.wallee_c2ec.client.taler + +import ch.bfh.habej2.wallee_c2ec.client.taler.model.BankWitdrawalOperationStatus +import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalApiConfig +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.TerminalWithdrawalSetupResponse +import ch.bfh.habej2.wallee_c2ec.client.taler.model.WithdrawalOperationStatus +import java.util.Optional + +interface TerminalClient { + + fun terminalsConfig(): Optional<TerminalApiConfig> + + fun setupWithdrawal(setupReq: TerminalWithdrawalSetup): Optional<TerminalWithdrawalSetupResponse> + + fun retrieveWithdrawalStatus( + wopid: String, + longPollMs: Int, + oldState: WithdrawalOperationStatus = WithdrawalOperationStatus.PENDING + ): Optional<BankWitdrawalOperationStatus> + + fun sendConfirmationRequest(encodedWopid: String, confirmationRequest: TerminalWithdrawalConfirmationRequest) + + fun abortWithdrawal(encodedWopid: String) +} +\ 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 @@ -1,6 +1,7 @@ package ch.bfh.habej2.wallee_c2ec.client.taler import ch.bfh.habej2.wallee_c2ec.client.taler.config.TalerTerminalConfig +import ch.bfh.habej2.wallee_c2ec.client.taler.model.BankWitdrawalOperationStatus import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalApiConfig import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalConfirmationRequest import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalSetup @@ -9,11 +10,9 @@ import ch.bfh.habej2.wallee_c2ec.client.taler.model.WithdrawalOperationStatus import com.squareup.moshi.Moshi import okhttp3.HttpUrl import okhttp3.Interceptor -import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import java.util.Optional @@ -25,7 +24,7 @@ class TerminalClientException( class TerminalClientImplementation ( private val config: TalerTerminalConfig -) { +): TerminalClient { private val client: OkHttpClient = OkHttpClient.Builder() @@ -60,7 +59,7 @@ class TerminalClientImplementation ( .addPathSegment("abort") .build() - fun terminalsConfig(): Optional<TerminalApiConfig> { + override fun terminalsConfig(): Optional<TerminalApiConfig> { val req = Request.Builder() .get() .url(terminalsConfigUrl()) @@ -69,7 +68,7 @@ class TerminalClientImplementation ( return parseOrEmpty(response) } - fun setupWithdrawal(setupReq: TerminalWithdrawalSetup): Optional<TerminalWithdrawalSetupResponse> { + override fun setupWithdrawal(setupReq: TerminalWithdrawalSetup): Optional<TerminalWithdrawalSetupResponse> { val reqBody = serializer(TerminalWithdrawalSetup::class.java) .toJson(setupReq) @@ -82,11 +81,11 @@ class TerminalClientImplementation ( return parseOrEmpty(response) } - fun retrieveWithdrawalStatus( + override fun retrieveWithdrawalStatus( wopid: String, longPollMs: Int, - oldState: WithdrawalOperationStatus = WithdrawalOperationStatus.PENDING - ): Optional<TerminalWithdrawalSetup> { + oldState: WithdrawalOperationStatus + ): Optional<BankWitdrawalOperationStatus> { val req = Request.Builder() .get() @@ -101,7 +100,10 @@ class TerminalClientImplementation ( return parseOrEmpty(response) } - fun sendConfirmationRequest(encodedWopid: String, confirmationRequest: TerminalWithdrawalConfirmationRequest) { + override fun sendConfirmationRequest( + encodedWopid: String, + confirmationRequest: TerminalWithdrawalConfirmationRequest + ) { val reqBody = serializer(TerminalWithdrawalConfirmationRequest::class.java) .toJson(confirmationRequest) @@ -116,7 +118,7 @@ class TerminalClientImplementation ( } } - fun abortWithdrawal(encodedWopid: String) { + override fun abortWithdrawal(encodedWopid: String) { val req = Request.Builder() .delete() .url(withdrawalsAbort(encodedWopid)) 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 @@ -0,0 +1,80 @@ +package ch.bfh.habej2.wallee_c2ec.client.taler + +import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtils.encodeCrock +import ch.bfh.habej2.wallee_c2ec.client.taler.model.BankWitdrawalOperationStatus +import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalApiConfig +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.TerminalWithdrawalSetupResponse +import ch.bfh.habej2.wallee_c2ec.client.taler.model.WithdrawalOperationStatus +import ch.bfh.habej2.wallee_c2ec.withdrawal.Amount +import java.security.SecureRandom +import java.util.Optional + +class TerminalClientMock: TerminalClient { + + override fun terminalsConfig() = + Optional.of( + TerminalApiConfig( + "taler-terminal", + "0:0:0", + "Wallee", + "CHF", + "wallee-transaction" + ) + ) + + override fun setupWithdrawal(setupReq: TerminalWithdrawalSetup) = + Optional.of( + TerminalWithdrawalSetupResponse( + mockWopidOrReservePubKey() + ) + ) + + override fun retrieveWithdrawalStatus( + wopid: String, + longPollMs: Int, + oldState: WithdrawalOperationStatus + ): Optional<BankWitdrawalOperationStatus> { + + Thread.sleep(longPollMs.toLong()) + return Optional.of( + BankWitdrawalOperationStatus( + WithdrawalOperationStatus.SELECTED, + Amount(10,0), + Amount(10,0), + Amount(0,0), + Amount(0,0), + "wallee-transaction", + "http://mock.com/api", + "http://mock.com/api", + "", + Array(0) {""}, + mockWopidOrReservePubKey(), + "payto://IBAN/CH1111111111111", + aborted = false, + selectionDone = true, + transferDone = false + ) + ) + } + + override fun sendConfirmationRequest( + encodedWopid: String, + confirmationRequest: TerminalWithdrawalConfirmationRequest + ) { + println("sending confirmation request") + } + + override fun abortWithdrawal(encodedWopid: String) { + println("aborting withdrawal") + } + + private fun mockWopidOrReservePubKey(): String { + + val wopid = ByteArray(32) + SecureRandom().nextBytes(wopid) + return encodeCrock(wopid) + + } +} +\ No newline at end of file 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 @@ -1,5 +1,6 @@ package ch.bfh.habej2.wallee_c2ec.withdrawal +import android.annotation.SuppressLint import android.app.Activity import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardOptions @@ -8,9 +9,11 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.KeyboardType +@SuppressLint("StateFlowValueCalledInComposition") @Composable fun AmountScreen(model: WithdrawalViewModel, navigateToWhenAmountEntered: () -> Unit) { @@ -20,30 +23,28 @@ fun AmountScreen(model: WithdrawalViewModel, navigateToWhenAmountEntered: () -> horizontalAlignment = Alignment.CenterHorizontally ) { - Text(text = "present card, trigger payment") + Text(model.uiState.value.amountError, color = Color.Red) TextField( - value = "", + value = model.uiState.value.amountInput, onValueChange = { - println(it) - //model.updateAmount(it) + model.validateInputOrEmptyField(it) }, label = { Text(text = "Enter amount") }, - placeholder = { Text(text = "amount") }, keyboardOptions = KeyboardOptions( autoCorrect = false, keyboardType = KeyboardType.Number ) ) - Button(onClick = { + Button(onClick = { val success = model.setupWithdrawal() if (!success) { activity.finish() } model.withdrawalOperationFailed() navigateToWhenAmountEntered() - }) { + }, enabled = (model.uiState.value.amount.value > 0 || model.uiState.value.amount.frac > 0)) { Text(text = "pay") } 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 @@ -21,7 +21,6 @@ class WithdrawalActivity : ComponentActivity() { setContent { - val navController = rememberNavController() NavHost(navController = navController, startDestination = "chooseExchangeScreen") { 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 @@ -8,7 +8,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import ch.bfh.habej2.wallee_c2ec.client.taler.TerminalClient import ch.bfh.habej2.wallee_c2ec.client.taler.TerminalClientImplementation +import ch.bfh.habej2.wallee_c2ec.client.taler.TerminalClientMock import ch.bfh.habej2.wallee_c2ec.client.taler.config.TalerTerminalConfig import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalConfirmationRequest import ch.bfh.habej2.wallee_c2ec.client.taler.model.TerminalWithdrawalSetup @@ -26,7 +28,12 @@ data class Amount( val value: Int, val frac: Int ) { - fun toBigDecimal(): BigDecimal = BigDecimal("$value.$frac") + + // fun toBigDecimal(): BigDecimal = BigDecimal("$value.$frac") + + override fun toString(): String { + return "$value.$frac" + } } @Stable @@ -34,7 +41,9 @@ interface WithdrawalOperationState{ val requestUid: String val exchangeBankIntegrationApiUrl: String val encodedWopid: String + val amountInput: String val amount: Amount + val amountError: String val currency: String val payed: Boolean val transaction: TransactionCompletionResponse? @@ -44,7 +53,9 @@ 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 amountInput: String by mutableStateOf("") override var amount: Amount by mutableStateOf(Amount(0,0)) + override var amountError: String by mutableStateOf("") override var currency: String by mutableStateOf("") override var payed: Boolean by mutableStateOf(false) override var transaction: TransactionCompletionResponse? by mutableStateOf(null) @@ -54,13 +65,13 @@ class WithdrawalViewModel( vararg closeables: Closeable ) : ViewModel(*closeables) { - private var terminalClient: TerminalClientImplementation? = null + private var terminalClient: TerminalClient? = null private val _uiState = MutableStateFlow(MutableWithdrawalOperationState()) val uiState: StateFlow<WithdrawalOperationState> = _uiState fun exchangeUpdated(activity: Activity, cfg: TalerTerminalConfig) { - terminalClient = TerminalClientImplementation(cfg) + terminalClient = TerminalClientMock() // TerminalClientImplementation(cfg) _uiState.value = MutableWithdrawalOperationState() // reset withdrawal operation val optionalApiCfg = terminalClient!!.terminalsConfig() if (!optionalApiCfg.isPresent) { @@ -86,8 +97,20 @@ class WithdrawalViewModel( return true } + fun validateInputOrEmptyField(amount: String) { + + val validAmount = parseAmount(amount) + if (validAmount.isPresent) { + _uiState.value.amountError = "" + _uiState.value.amount = validAmount.get() + } else { + _uiState.value.amountInput = "" + _uiState.value.amountError = "invalid amount (format: X[.X])" + } + } + fun updateAmount(amount: String) { - _uiState.value.amount = parseAmount(amount).orElse(null) + _uiState.value.amount = parseAmount(amount).orElse(Amount(0,0)) } private fun updateCurrency(currency: String) {