gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-cashless2ecash] branch master updated (fd19956 -> ad94bc5)


From: gnunet
Subject: [taler-cashless2ecash] branch master updated (fd19956 -> ad94bc5)
Date: Fri, 26 Apr 2024 16:13:59 +0200

This is an automated email from the git hooks/post-receive script.

joel-haeberli pushed a change to branch master
in repository cashless2ecash.

    from fd19956  app: update flow
     new 76246c3  fix: crockford encoding
     new ad94bc5  fix: implement terminal api, enable basic auth, fix simulation

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 c2ec/{auth.go => api-auth.go}                      |  84 +++--
 c2ec/{auth_test.go => api-auth_test.go}            |   0
 c2ec/api-bank-integration.go                       | 252 ++++++++++++++
 c2ec/api-terminals.go                              | 243 ++++++++++++++
 c2ec/{wire-gateway.go => api-wire-gateway.go}      | 148 ++------
 c2ec/bank-integration.go                           | 371 ---------------------
 c2ec/{postgres.go => db-postgres.go}               |  95 +++++-
 c2ec/db.go                                         |  32 +-
 c2ec/db/0001-c2ec_schema.sql                       |  20 +-
 c2ec/db/drop.sql                                   |   2 +
 .../db/proc-c2ec_payment_notification_listener.sql |   4 +-
 c2ec/db/proc-c2ec_retry_listener.sql               |   2 +-
 c2ec/db/proc-c2ec_status_listener.sql              |   4 +-
 c2ec/db/procedures.sql                             | 162 ---------
 c2ec/db/procedures.sql.in                          |   1 -
 ...test_c2ec_test.sql => test_c2ec_simulation.sql} |   0
 ...lback.sql => test_c2ec_simulation_rollback.sql} |   0
 c2ec/encoding.go                                   | 127 +++++--
 c2ec/encoding_test.go                              |  12 +-
 c2ec/http-util.go                                  |  48 +--
 c2ec/install/build_app.sh                          |  21 ++
 c2ec/install/installation_notes.md                 |  33 ++
 c2ec/install/setup_db.sh                           |  37 ++
 c2ec/install/start.sh                              |  25 ++
 c2ec/install/wipe_db.sh                            |  27 ++
 c2ec/main.go                                       |  39 ++-
 c2ec/{attestor.go => proc-attestor.go}             |   4 +-
 c2ec/{listener.go => proc-listener.go}             |   0
 c2ec/{transfer.go => proc-transfer.go}             |   0
 cli/cli.go                                         |  99 +++++-
 cli/db.go                                          |   2 +-
 .../bank-integration.tex}                          |   0
 docs/content/implementation/terminal-api.tex       |  27 ++
 .../{.gitkeep => implementation/wire-gateway.tex}  |   0
 docs/project.bib                                   |   7 +
 simulation/c2ec-simulation                         | Bin 7570759 -> 7648407 
bytes
 simulation/encoding.go                             | 138 ++++++--
 simulation/go.mod                                  |   2 +
 simulation/go.sum                                  |   2 +
 simulation/http-util.go                            | 110 ++----
 simulation/main.go                                 |  24 --
 simulation/model.go                                |   7 -
 simulation/sim-terminal.go                         | 182 +++++++---
 simulation/sim-wallet.go                           |  52 ++-
 wallee-c2ec/app/src/main/AndroidManifest.xml       |   2 +
 .../client/taler/encoding/CryptoUtils.kt           | 105 ++++++
 .../client/taler/encoding/TalerBase32Codec.kt      |  16 +-
 .../client/wallee/WalleeResponseHandler.kt         |  14 +-
 .../habej2/wallee_c2ec/withdrawal/AmountScreen.kt  |  51 +++
 .../withdrawal/AuthorizePaymentScreen.kt           |  57 ++++
 .../withdrawal/ExchangeSelectionScreen.kt          |  42 +++
 .../withdrawal/RegisterWithdrawalScreen.kt         |  44 +++
 .../wallee_c2ec/withdrawal/WithdrawalActivity.kt   | 159 ++-------
 .../client/taler/encoding/CyptoUtilsTest.kt        |  54 +++
 54 files changed, 1848 insertions(+), 1141 deletions(-)
 rename c2ec/{auth.go => api-auth.go} (73%)
 rename c2ec/{auth_test.go => api-auth_test.go} (100%)
 create mode 100644 c2ec/api-bank-integration.go
 create mode 100644 c2ec/api-terminals.go
 rename c2ec/{wire-gateway.go => api-wire-gateway.go} (69%)
 delete mode 100644 c2ec/bank-integration.go
 rename c2ec/{postgres.go => db-postgres.go} (88%)
 delete mode 100644 c2ec/db/procedures.sql
 delete mode 100644 c2ec/db/procedures.sql.in
 rename c2ec/db/{test_c2ec_test.sql => test_c2ec_simulation.sql} (100%)
 rename c2ec/db/{test_c2ec_test_rollback.sql => 
test_c2ec_simulation_rollback.sql} (100%)
 create mode 100644 c2ec/install/build_app.sh
 create mode 100644 c2ec/install/installation_notes.md
 create mode 100755 c2ec/install/setup_db.sh
 create mode 100755 c2ec/install/start.sh
 create mode 100755 c2ec/install/wipe_db.sh
 rename c2ec/{attestor.go => proc-attestor.go} (98%)
 rename c2ec/{listener.go => proc-listener.go} (100%)
 rename c2ec/{transfer.go => proc-transfer.go} (100%)
 copy docs/content/{.gitkeep => implementation/bank-integration.tex} (100%)
 create mode 100644 docs/content/implementation/terminal-api.tex
 copy docs/content/{.gitkeep => implementation/wire-gateway.tex} (100%)
 create mode 100644 simulation/go.sum
 create mode 100644 
wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CryptoUtils.kt
 create mode 100644 
wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AmountScreen.kt
 create mode 100644 
wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AuthorizePaymentScreen.kt
 create mode 100644 
wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ExchangeSelectionScreen.kt
 create mode 100644 
wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterWithdrawalScreen.kt
 create mode 100644 
wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CyptoUtilsTest.kt

diff --git a/c2ec/auth.go b/c2ec/api-auth.go
similarity index 73%
rename from c2ec/auth.go
rename to c2ec/api-auth.go
index b008f31..cfcb985 100644
--- a/c2ec/auth.go
+++ b/c2ec/api-auth.go
@@ -45,16 +45,19 @@ func AuthenticateTerminal(req *http.Request) bool {
 
                decoded, err := base64.StdEncoding.DecodeString(basicAuth)
                if err != nil {
+                       LogWarn("auth", "failed decoding basic auth header from 
base64")
                        return false
                }
 
                username, password, err := parseBasicAuth(string(decoded))
                if err != nil {
+                       LogWarn("auth", "failed parsing username password from 
basic auth")
                        return false
                }
 
                provider, terminalId, err := parseTerminalUser(username)
                if err != nil {
+                       LogWarn("auth", "failed parsing terminal from username 
in basic auth")
                        return false
                }
                LogInfo("auth", fmt.Sprintf("req=%s by terminal with id=%d, 
provider=%s", req.RequestURI, terminalId, provider))
@@ -86,28 +89,6 @@ func AuthenticateTerminal(req *http.Request) bool {
        return false
 }
 
-// find out how the wallet authenticates itself.
-// returns true if authentication was successful, otherwise false
-// when not successful, the api shall return immediately
-func AuthenticateWallet(req *http.Request) bool {
-
-       // Is this needed? Understand how the wallet authenticates itself at 
the exchange currently first.
-       // https://docs.taler.net/design-documents/049-auth.html#dd48-token
-       // https://docs.taler.net/core/api-corebank.html#authentication
-       //
-       // /accounts/$USERNAME/token
-       //
-       // The username in our case is the reserve public key
-       // registered for withdrawal. At the initial registration
-       // of the reserve public key we leverage a TOFU trust model.
-       // during the registration of the reserve public key a new
-       // access token will be created with a limited lifetime.
-       // The token will not be refreshable and become invalid
-       // only after a few minutes. Since the Wallet will register
-       // a wopid and
-       return true
-}
-
 func parseBasicAuth(basicAuth string) (string, string, error) {
 
        parts := strings.Split(basicAuth, ":")
@@ -137,6 +118,65 @@ func parseTerminalUser(username string) (string, int, 
error) {
        return providerName, terminalId, nil
 }
 
+// Parses the terminal id from the token.
+// This function is used to determine the terminal
+// which orchestrates the withdrawal.
+func parseTerminalId(req *http.Request) int {
+       auth := req.Header.Get(AUTHORIZATION_HEADER)
+       if basicAuth, found := strings.CutPrefix(auth, BASIC_AUTH_PREFIX); 
found {
+
+               decoded, err := base64.StdEncoding.DecodeString(basicAuth)
+               if err != nil {
+                       return -1
+               }
+
+               username, _, err := parseBasicAuth(string(decoded))
+               if err != nil {
+                       return -1
+               }
+
+               _, terminalId, err := parseTerminalUser(username)
+               if err != nil {
+                       return -1
+               }
+
+               return terminalId
+       }
+
+       return -1
+}
+
+func parseProvider(req *http.Request) (*Provider, error) {
+
+       auth := req.Header.Get(AUTHORIZATION_HEADER)
+       if basicAuth, found := strings.CutPrefix(auth, BASIC_AUTH_PREFIX); 
found {
+
+               decoded, err := base64.StdEncoding.DecodeString(basicAuth)
+               if err != nil {
+                       return nil, err
+               }
+
+               username, _, err := parseBasicAuth(string(decoded))
+               if err != nil {
+                       return nil, err
+               }
+
+               providerName, _, err := parseTerminalUser(username)
+               if err != nil {
+                       return nil, err
+               }
+
+               p, err := DB.GetTerminalProviderByName(providerName)
+               if err != nil {
+                       return nil, err
+               }
+
+               return p, nil
+       }
+
+       return nil, errors.New("authorization header did not match 
expectations")
+}
+
 // takes a password and a base64 encoded password hash, including salt and 
checks
 // the password supplied against it.
 // the format of the password hash is expected to be the following:
diff --git a/c2ec/auth_test.go b/c2ec/api-auth_test.go
similarity index 100%
rename from c2ec/auth_test.go
rename to c2ec/api-auth_test.go
diff --git a/c2ec/api-bank-integration.go b/c2ec/api-bank-integration.go
new file mode 100644
index 0000000..ec7044b
--- /dev/null
+++ b/c2ec/api-bank-integration.go
@@ -0,0 +1,252 @@
+package main
+
+import (
+       "bytes"
+       "context"
+       "encoding/base64"
+       "fmt"
+       http "net/http"
+       "strconv"
+       "time"
+)
+
+const WITHDRAWAL_OPERATION = "/withdrawal-operation"
+
+const WOPID_PARAMETER = "wopid"
+const BANK_INTEGRATION_CONFIG_PATTERN = "/config"
+const WITHDRAWAL_OPERATION_PATTERN = WITHDRAWAL_OPERATION
+const WITHDRAWAL_OPERATION_BY_WOPID_PATTERN = WITHDRAWAL_OPERATION + "/{" + 
WOPID_PARAMETER + "}"
+const WITHDRAWAL_OPERATION_ABORTION_PATTERN = 
WITHDRAWAL_OPERATION_BY_WOPID_PATTERN + "/abort"
+
+const DEFAULT_LONG_POLL_MS = 1000
+const DEFAULT_OLD_STATE = PENDING
+
+// 
https://docs.taler.net/core/api-exchange.html#tsref-type-CurrencySpecification
+type CurrencySpecification struct {
+       Name                            string `json:"name"`
+       Currency                        string `json:"currency"`
+       NumFractionalInputDigits        int    
`json:"num_fractional_input_digits"`
+       NumFractionalNormalDigits       int    
`json:"num_fractional_normal_digits"`
+       NumFractionalTrailingZeroDigits int    
`json:"num_fractional_trailing_zero_digits"`
+       AltUnitNames                    string `json:"alt_unit_names"`
+}
+
+// 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankIntegrationConfig
+type BankIntegrationConfig struct {
+       Name                  string                `json:"name"`
+       Version               string                `json:"version"`
+       Implementation        string                `json:"implementation"`
+       Currency              string                `json:"currency"`
+       CurrencySpecification CurrencySpecification 
`json:"currency_specification"`
+       // TODO: maybe add exchanges payto uri for transfers etc.?
+}
+
+type BankWithdrawalOperationPostRequest struct {
+       ReservePubKey    EddsaPublicKey `json:"reserve_pub"`
+       SelectedExchange string         `json:"selected_exchange"`
+       Amount           *Amount        `json:"amount"`
+}
+
+type BankWithdrawalOperationPostResponse struct {
+       Status             WithdrawalOperationStatus `json:"status"`
+       ConfirmTransferUrl string                    
`json:"confirm_transfer_url"`
+       TransferDone       bool                      `json:"transfer_done"`
+}
+
+type BankWithdrawalOperationStatus struct {
+       Status        WithdrawalOperationStatus `json:"status"`
+       Amount        Amount                    `json:"amount"`
+       SenderWire    string                    `json:"sender_wire"`
+       WireTypes     []string                  `json:"wire_types"`
+       ReservePubKey EddsaPublicKey            `json:"selected_reserve_pub"`
+}
+
+func bankIntegrationConfig(res http.ResponseWriter, req *http.Request) {
+
+       cfg := BankIntegrationConfig{
+               Name:    "taler-bank-integration",
+               Version: "0:0:1",
+       }
+
+       serializedCfg, err := 
NewJsonCodec[BankIntegrationConfig]().EncodeToBytes(&cfg)
+       if err != nil {
+               LogInfo("bank-integration-api", fmt.Sprintf("failed serializing 
config: %s", err.Error()))
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               return
+       }
+
+       res.WriteHeader(HTTP_OK)
+       res.Write(serializedCfg)
+}
+
+func handleParameterRegistration(res http.ResponseWriter, req *http.Request) {
+
+       jsonCodec := NewJsonCodec[BankWithdrawalOperationPostRequest]()
+       registration, err := ReadStructFromBody(req, jsonCodec)
+       if err != nil {
+               LogWarn("bank-integration-api", fmt.Sprintf("invalid body for 
withdrawal registration error=%s", err.Error()))
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       // read and validate the wopid path parameter
+       wopid := req.PathValue(WOPID_PARAMETER)
+       wpd, err := ParseWopid(wopid)
+       if err != nil {
+               LogWarn("bank-integration-api", "wopid "+wopid+" not valid")
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       if _, err = DB.GetWithdrawalByWopid(wpd); err != nil {
+               LogError("bank-integration-api", err)
+               res.WriteHeader(HTTP_NOT_FOUND)
+               return
+       }
+
+       if err = DB.RegisterWithdrawalParameters(
+               wpd,
+               registration.ReservePubKey,
+       ); err != nil {
+               LogError("bank-integration-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               return
+       }
+
+       withdrawal, err := DB.GetWithdrawalByWopid(wpd)
+       if err != nil {
+               LogError("bank-integration-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+       }
+
+       resbody := &BankWithdrawalOperationPostResponse{
+               Status:             withdrawal.WithdrawalStatus,
+               ConfirmTransferUrl: "", // not used in our case
+               TransferDone:       withdrawal.WithdrawalStatus == CONFIRMED,
+       }
+
+       resbyts, err := 
NewJsonCodec[BankWithdrawalOperationPostResponse]().EncodeToBytes(resbody)
+       if err != nil {
+               LogError("bank-integration-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+       }
+
+       res.Write(resbyts)
+}
+
+// Get status of withdrawal associated with the given WOPID
+//
+// Parameters:
+//   - long_poll_ms (optional):
+//     milliseconds to wait for state to change
+//     given old_state until responding
+//   - old_state (optional):
+//     Default is 'pending'
+func handleWithdrawalStatus(res http.ResponseWriter, req *http.Request) {
+
+       // read and validate request query parameters
+       shouldStartLongPoll := true
+       longPollMilli := DEFAULT_LONG_POLL_MS
+       if longPollMilliPtr, accepted := AcceptOptionalParamOrWriteResponse(
+               "long_poll_ms", strconv.Atoi, req, res,
+       ); accepted {
+               if longPollMilliPtr != nil {
+                       longPollMilli = *longPollMilliPtr
+               } else {
+                       // this means parameter was not given.
+                       // no long polling (simple get)
+                       shouldStartLongPoll = false
+               }
+       } else {
+               shouldStartLongPoll = false
+       }
+
+       // read and validate the wopid path parameter
+       wopid := req.PathValue(WOPID_PARAMETER)
+       wpd, err := ParseWopid(wopid)
+       if err != nil {
+               LogWarn("bank-integration-api", "wopid "+wopid+" not valid")
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       if shouldStartLongPoll {
+
+               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(
+                       channel,
+                       notifications,
+               )
+
+               if err != nil {
+                       res.WriteHeader(HTTP_NO_CONTENT)
+                       return
+               }
+
+               go listenFunc(timeoutCtx)
+
+               for {
+                       select {
+                       case <-timeoutCtx.Done():
+                               LogInfo("bank-integration-api", "long poll time 
exceeded")
+                               res.WriteHeader(HTTP_NO_CONTENT)
+                               return
+                       case <-notifications:
+                               writeWithdrawalOrError(wpd, res)
+                               return
+                       }
+               }
+       }
+
+       writeWithdrawalOrError(wpd, res)
+}
+
+func handleWithdrawalAbort(res http.ResponseWriter, req *http.Request) {
+       res.WriteHeader(HTTP_OK)
+       res.Write(bytes.NewBufferString("retrieved withdrawal operation 
abortion request").Bytes())
+}
+
+// Tries to load a WithdrawalOperationStatus from the database. If no
+// entry could been found, it will write the correct error to the response.
+func writeWithdrawalOrError(wopid []byte, res http.ResponseWriter) {
+       // read the withdrawal from the database
+       withdrawal, err := DB.GetWithdrawalByWopid(wopid)
+       if err != nil {
+               LogError("bank-integration-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               return
+       }
+
+       if withdrawal == nil {
+               // not found -> 404
+               res.WriteHeader(HTTP_NOT_FOUND)
+               return
+       }
+
+       // return the C2ECWithdrawalStatus
+       if amount, err := ToAmount(withdrawal.Amount); err != nil {
+               LogError("bank-integration-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               return
+       } else {
+               withdrawalStatusBytes, err := 
NewJsonCodec[BankWithdrawalOperationStatus]().EncodeToBytes(&BankWithdrawalOperationStatus{
+                       Status: withdrawal.WithdrawalStatus,
+                       Amount: *amount,
+               })
+               if err != nil {
+                       LogError("bank-integration-api", err)
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+                       return
+               }
+               res.WriteHeader(HTTP_OK)
+               res.Write(withdrawalStatusBytes)
+       }
+}
diff --git a/c2ec/api-terminals.go b/c2ec/api-terminals.go
new file mode 100644
index 0000000..b120a7e
--- /dev/null
+++ b/c2ec/api-terminals.go
@@ -0,0 +1,243 @@
+package main
+
+import (
+       "crypto/rand"
+       "fmt"
+       "net/http"
+)
+
+const TERMINAL_API_CONFIG = "/config"
+const TERMINAL_API_REGISTER_WITHDRAWAL = "/withdrawals"
+const TERMINAL_API_WITHDRAWAL_STATUS = "/withdrawals/{wopid}"
+const TERMINAL_API_CHECK_WITHDRAWAL = "/withdrawals/{wopid}/check"
+
+type TerminalConfig struct {
+       Name         string `json:"name"`
+       Version      string `json:"version"`
+       ProviderName string `json:"provider_name"`
+       WireType     string `json:"wire_type"`
+}
+
+type TerminalWithdrawalSetup struct {
+       Amount                *Amount `json:"amount"`
+       SuggestedAmount       *Amount `json:"suggested_amount"`
+       ProviderTransactionId string  `json:"provider_transaction_id"`
+       TerminalFees          *Amount `json:"terminal_fees"`
+       RequestUid            string  `json:"request_uid"`
+       UserUuid              string  `json:"user_uuid"`
+       Lock                  string  `json:"lock"`
+}
+
+type TerminalWithdrawalSetupResponse struct {
+       Wopid string `json:"withdrawal_id"`
+}
+
+type TerminalWithdrawalConfirmationRequest struct {
+       ProviderTransactionId string  `json:"provider_transaction_id"`
+       TerminalFees          *Amount `json:"terminal_fees"`
+       UserUuid              string  `json:"user_uuid"`
+       Lock                  string  `json:"lock"`
+}
+
+func handleTerminalConfig(res http.ResponseWriter, req *http.Request) {
+
+       p, auth, err := authAndParseProvider(req)
+       if !auth {
+               res.WriteHeader(HTTP_UNAUTHORIZED)
+               return
+       }
+
+       if err != nil || p == nil {
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       cfg, err := 
NewJsonCodec[TerminalConfig]().EncodeToBytes(&TerminalConfig{
+               Name:         "taler-terminal",
+               Version:      "0:0:0",
+               ProviderName: p.Name,
+               WireType:     p.PaytoTargetType,
+       })
+       if err != nil {
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               return
+       }
+       res.WriteHeader(HTTP_OK)
+       res.Write(cfg)
+}
+
+func handleWithdrawalSetup(res http.ResponseWriter, req *http.Request) {
+
+       p, auth, err := authAndParseProvider(req)
+       if !auth {
+               res.WriteHeader(HTTP_UNAUTHORIZED)
+               return
+       }
+       if err != nil || p == nil {
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       jsonCodec := NewJsonCodec[TerminalWithdrawalSetup]()
+       setup, err := ReadStructFromBody(req, jsonCodec)
+       if err != nil {
+               LogWarn("terminals-api", fmt.Sprintf("invalid body for 
withdrawal registration error=%s", err.Error()))
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       if hasConflict(setup) {
+               res.WriteHeader(HTTP_CONFLICT)
+               return
+       }
+
+       // generate wopid
+       generatedWopid := make([]byte, 32)
+       _, err = rand.Read(generatedWopid)
+       if err != nil {
+               LogWarn("terminals-api", "unable to generate correct wopid")
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+       }
+
+       err = DB.SetupWithdrawal(
+               generatedWopid,
+               preventNilAmount(setup.SuggestedAmount),
+               preventNilAmount(setup.Amount),
+               setup.ProviderTransactionId,
+               preventNilAmount(setup.TerminalFees),
+               setup.RequestUid,
+       )
+
+       if err != nil {
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               return
+       }
+
+       encodedBody, err := 
NewJsonCodec[TerminalWithdrawalSetupResponse]().EncodeToBytes(
+               &TerminalWithdrawalSetupResponse{
+                       Wopid: talerBinaryEncode(generatedWopid),
+               },
+       )
+       if err != nil {
+               LogError("terminal-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               return
+       }
+
+       res.Write(encodedBody)
+}
+
+func handleWithdrawalCheck(res http.ResponseWriter, req *http.Request) {
+
+       p, auth, err := authAndParseProvider(req)
+       if !auth {
+               res.WriteHeader(HTTP_UNAUTHORIZED)
+               return
+       }
+
+       if err != nil || p == nil {
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       wopid := req.PathValue(WOPID_PARAMETER)
+       wpd, err := ParseWopid(wopid)
+       if err != nil {
+               LogWarn("bank-integration-api", "wopid "+wopid+" not valid")
+               if wopid == "" {
+                       res.WriteHeader(HTTP_BAD_REQUEST)
+                       return
+               }
+       }
+
+       jsonCodec := NewJsonCodec[TerminalWithdrawalConfirmationRequest]()
+       paymentNotification, err := ReadStructFromBody(req, jsonCodec)
+       if err != nil {
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       LogInfo("bank-integration-api", "received payment notification")
+
+       terminalId := parseTerminalId(req)
+       if terminalId == -1 {
+               LogWarn("terminals-api", "terminal id could not be read from 
authorization header")
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       if paymentNotification.TerminalFees == nil {
+               paymentNotification.TerminalFees = &Amount{"", 0, 0}
+       }
+
+       err = DB.NotifyPayment(
+               wpd,
+               paymentNotification.ProviderTransactionId,
+               terminalId,
+               *paymentNotification.TerminalFees,
+       )
+       if err != nil {
+               LogError("terminals-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
+               return
+       }
+
+       res.WriteHeader(HTTP_NO_CONTENT)
+}
+
+func preventNilAmount(a *Amount) Amount {
+
+       if a == nil {
+               return Amount{"", 0, 0}
+       }
+
+       return *a
+}
+
+func hasConflict(t *TerminalWithdrawalSetup) bool {
+
+       w, err := DB.GetWithdrawalByRequestUid(t.RequestUid)
+       if err != nil {
+               LogError("terminals-api", err)
+               return true
+       }
+
+       if w == nil {
+               return false // no request with this uid
+       }
+
+       isEqual := w.Amount.Curr == t.Amount.Currency &&
+               w.Amount.Val == int64(t.Amount.Value) &&
+               w.Amount.Frac == int32(t.Amount.Fraction) &&
+               w.TerminalFees.Curr == t.TerminalFees.Currency &&
+               uint64(w.TerminalFees.Val) == t.TerminalFees.Value &&
+               uint64(w.TerminalFees.Frac) == t.TerminalFees.Fraction &&
+               w.SuggestedAmount.Curr == t.SuggestedAmount.Currency &&
+               uint64(w.SuggestedAmount.Val) == t.SuggestedAmount.Value &&
+               uint64(w.SuggestedAmount.Frac) == t.SuggestedAmount.Fraction &&
+               w.ProviderTransactionId == &t.ProviderTransactionId &&
+               w.RequestUid == t.RequestUid
+
+       return !isEqual
+}
+
+func authAndParseProvider(req *http.Request) (*Provider, bool, error) {
+
+       if authenticated := AuthenticateTerminal(req); !authenticated {
+               return nil, false, nil
+       }
+
+       p, err := parseProvider(req)
+       if err != nil {
+               return nil, true, err
+       }
+
+       return p, true, nil
+}
diff --git a/c2ec/wire-gateway.go b/c2ec/api-wire-gateway.go
similarity index 69%
rename from c2ec/wire-gateway.go
rename to c2ec/api-wire-gateway.go
index 0e793af..41cd2b5 100644
--- a/c2ec/wire-gateway.go
+++ b/c2ec/api-wire-gateway.go
@@ -103,7 +103,7 @@ func NewIncomingReserveTransaction(w *Withdrawal) 
*IncomingReserveTransaction {
        }
        t.DebitAccount = client.FormatPayto(w)
        t.ReservePub = FormatEddsaPubKey(w.ReservePubKey)
-       t.RowId = int(w.WithdrawalId)
+       t.RowId = int(w.WithdrawalRowId)
        t.Type = INCOMING_RESERVE_TRANSACTION_TYPE
        return t
 }
@@ -148,72 +148,36 @@ func transfer(res http.ResponseWriter, req *http.Request) 
{
        jsonCodec := NewJsonCodec[TransferRequest]()
        transfer, err := ReadStructFromBody(req, jsonCodec)
        if err != nil {
-
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_TRANSFER_INVALID_REQ",
-                       Title:    "invalid request",
-                       Detail:   "the transfer request is malformed (error: " 
+ err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
                return
        }
 
        paytoTargetType, tid, err := 
ParsePaytoWalleeTransaction(transfer.CreditAccount)
        if err != nil {
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_TRANSFER_INVALID_REQ",
-                       Title:    "invalid payto-uri",
-                       Detail:   "the transfer request contains an invalid 
payto-uri (error: " + err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
                return
        }
 
        p, err := DB.GetTerminalProviderByPaytoTargetType(paytoTargetType)
        if err != nil {
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
-                       Title:    "database request failed",
-                       Detail:   "failed to retrieve the provider for the 
payto target type '" + paytoTargetType + "'",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
-       decodedRequestUid, err := talerBase32Decode(string(transfer.RequestUid))
+       decodedRequestUid, err := talerBinaryDecode(string(transfer.RequestUid))
        if err != nil {
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_TRANSFER_INVALID_REQ",
-                       Title:    "invalid request",
-                       Detail:   "the transfer request is malformed (error: " 
+ err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_BAD_REQUEST)
                return
        }
 
        t, err := DB.GetTransferById(decodedRequestUid)
        if err != nil {
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
-                       Title:    "database request failed",
-                       Detail:   "there was an error processing the database 
query",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
@@ -227,15 +191,8 @@ func transfer(res http.ResponseWriter, req *http.Request) {
                        transfer.CreditAccount,
                )
                if err != nil {
-                       err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
-                               Title:    "database request failed",
-                               Detail:   "there was an error creating the 
transfer",
-                               Instance: req.RequestURI,
-                       })
-                       if err != nil {
-                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                       }
+                       LogError("wire-gateway-api", err)
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                        return
                }
        } else {
@@ -247,44 +204,23 @@ func transfer(res http.ResponseWriter, req *http.Request) 
{
                        transfer.Wtid != ShortHashCode(t.Wtid) ||
                        transfer.CreditAccount != t.CreditAccount {
 
-                       err := WriteProblem(res, HTTP_CONFLICT, &RFC9457Problem{
-                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_TRANSFER_INVALID_REQ",
-                               Title:    "invalid request",
-                               Detail:   "the transfer request did not match 
previous request with the same request identifier",
-                               Instance: req.RequestURI,
-                       })
-                       if err != nil {
-                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                       }
+                       LogWarn("wire-gateway-api", "idempotency violation")
+                       res.WriteHeader(HTTP_CONFLICT)
                        return
                }
 
                ptid := strconv.Itoa(tid)
                w, err := DB.GetWithdrawalByProviderTransactionId(ptid)
                if err != nil || w == nil {
-                       err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
-                               Title:    "database request failed",
-                               Detail:   "there was an error processing the 
database query or no withdrawal could been found.",
-                               Instance: req.RequestURI,
-                       })
-                       if err != nil {
-                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                       }
+                       LogError("wire-gateway-api", err)
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                        return
                }
 
                refundClient := PROVIDER_CLIENTS[p.Name]
                if refundClient == nil {
-                       err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_UNKNOWN_TRANSFER_MECHANISM",
-                               Title:    "unknown refund mechanism",
-                               Detail:   "the target type of the payto uri for 
the transfer is not registered",
-                               Instance: req.RequestURI,
-                       })
-                       if err != nil {
-                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                       }
+                       LogError("wire-gateway-api", errors.New("client for 
provider "+p.Name+" not initialized"))
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                        return
                }
                refundClient.Refund(ptid)
@@ -365,15 +301,8 @@ func historyIncoming(res http.ResponseWriter, req 
*http.Request) {
        withdrawals, err := DB.GetConfirmedWithdrawals(start, delta)
 
        if err != nil {
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
-                       Title:    "database request failed",
-                       Detail:   "there was an error processing the database 
query. error=" + err.Error(),
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
@@ -392,15 +321,8 @@ func historyIncoming(res http.ResponseWriter, req 
*http.Request) {
 
        enc, err := 
NewJsonCodec[[]*IncomingReserveTransaction]().EncodeToBytes(&transactions)
        if err != nil {
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_RESPONSE_ENCODING_FAILED",
-                       Title:    "encoding failed",
-                       Detail:   "the encoding of the response failed (error:" 
+ err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
@@ -459,15 +381,8 @@ func historyOutgoing(res http.ResponseWriter, req 
*http.Request) {
        transfers, err := DB.GetTransfers(start, delta)
 
        if err != nil {
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
-                       Title:    "database request failed",
-                       Detail:   "there was an error processing the database 
query",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
@@ -495,15 +410,8 @@ func historyOutgoing(res http.ResponseWriter, req 
*http.Request) {
        }
        enc, err := 
NewJsonCodec[OutgoingHistory]().EncodeToBytes(&outgoingHistory)
        if err != nil {
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_RESPONSE_ENCODING_FAILED",
-                       Title:    "encoding failed",
-                       Detail:   "the encoding of the response failed (error:" 
+ err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               LogError("wire-gateway-api", err)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
diff --git a/c2ec/bank-integration.go b/c2ec/bank-integration.go
deleted file mode 100644
index 35710a6..0000000
--- a/c2ec/bank-integration.go
+++ /dev/null
@@ -1,371 +0,0 @@
-package main
-
-import (
-       "bytes"
-       "context"
-       "encoding/base64"
-       "fmt"
-       http "net/http"
-       "strconv"
-       "time"
-)
-
-const BANK_INTEGRATION_CONFIG_ENDPOINT = "/config"
-const WITHDRAWAL_OPERATION = "/withdrawal-operation"
-
-const WOPID_PARAMETER = "wopid"
-const BANK_INTEGRATION_CONFIG_PATTERN = BANK_INTEGRATION_CONFIG_ENDPOINT
-const WITHDRAWAL_OPERATION_PATTERN = WITHDRAWAL_OPERATION
-const WITHDRAWAL_OPERATION_BY_WOPID_PATTERN = WITHDRAWAL_OPERATION + "/{" + 
WOPID_PARAMETER + "}"
-const WITHDRAWAL_OPERATION_PAYMENT_PATTERN = 
WITHDRAWAL_OPERATION_BY_WOPID_PATTERN + "/confirm"
-const WITHDRAWAL_OPERATION_ABORTION_PATTERN = 
WITHDRAWAL_OPERATION_BY_WOPID_PATTERN + "/abort"
-
-const DEFAULT_LONG_POLL_MS = 1000
-const DEFAULT_OLD_STATE = PENDING
-
-// 
https://docs.taler.net/core/api-exchange.html#tsref-type-CurrencySpecification
-type CurrencySpecification struct {
-       Name                            string `json:"name"`
-       Currency                        string `json:"currency"`
-       NumFractionalInputDigits        int    
`json:"num_fractional_input_digits"`
-       NumFractionalNormalDigits       int    
`json:"num_fractional_normal_digits"`
-       NumFractionalTrailingZeroDigits int    
`json:"num_fractional_trailing_zero_digits"`
-       AltUnitNames                    string `json:"alt_unit_names"`
-}
-
-// 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankIntegrationConfig
-type BankIntegrationConfig struct {
-       Name                  string                `json:"name"`
-       Version               string                `json:"version"`
-       Implementation        string                `json:"implementation"`
-       Currency              string                `json:"currency"`
-       CurrencySpecification CurrencySpecification 
`json:"currency_specification"`
-       // TODO: maybe add exchanges payto uri for transfers etc.?
-}
-
-type C2ECWithdrawRegistration struct {
-       ReservePubKey EddsaPublicKey `json:"reserve_pub_key"`
-}
-
-type C2ECWithdrawalStatus struct {
-       Status        WithdrawalOperationStatus `json:"status"`
-       Amount        Amount                    `json:"amount"`
-       SenderWire    string                    `json:"sender_wire"`
-       WireTypes     []string                  `json:"wire_types"`
-       ReservePubKey EddsaPublicKey            `json:"selected_reserve_pub"`
-}
-
-type C2ECPaymentNotification struct {
-       ProviderTransactionId string `json:"provider_transaction_id"`
-       TerminalId            int    `json:"terminal_id"`
-       Amount                Amount `json:"amount"`
-       Fees                  Amount `json:"card_fees"`
-}
-
-func bankIntegrationConfig(res http.ResponseWriter, req *http.Request) {
-
-       cfg := BankIntegrationConfig{
-               Name:    "taler-bank-integration",
-               Version: "0:0:1",
-       }
-
-       serializedCfg, err := 
NewJsonCodec[BankIntegrationConfig]().EncodeToBytes(&cfg)
-       if err != nil {
-               LogInfo("bank-integration-api", fmt.Sprintf("failed serializing 
config: %s", err.Error()))
-               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               return
-       }
-
-       res.WriteHeader(HTTP_OK)
-       res.Write(serializedCfg)
-}
-
-func handleWithdrawalRegistration(res http.ResponseWriter, req *http.Request) {
-
-       jsonCodec := NewJsonCodec[C2ECWithdrawRegistration]()
-       registration, err := ReadStructFromBody(req, jsonCodec)
-       if err != nil {
-               LogWarn("bank-integration-api", fmt.Sprintf("invalid body for 
withdrawal registration error=%s", err.Error()))
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAW_REGISTRATION_INVALID_REQ",
-                       Title:    "invalid request",
-                       Detail:   "the registration request for the withdrawal 
is malformed (error: " + err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       // read and validate the wopid path parameter
-       wopid := req.PathValue(WOPID_PARAMETER)
-       wpd, err := ParseWopid(wopid)
-       if err != nil {
-               LogWarn("bank-integration-api", "wopid "+wopid+" not valid")
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_INVALID_PATH_PARAMETER",
-                       Title:    "invalid request path parameter",
-                       Detail:   "the withdrawal status request path parameter 
'wopid' is malformed",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       err = DB.RegisterWithdrawal(
-               wpd,
-               registration.ReservePubKey,
-       )
-
-       if err != nil {
-
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAW_REGISTRATION_DB_FAILURE",
-                       Title:    "database failure",
-                       Detail:   "the registration of the withdrawal failed 
due to db failure (error:" + err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       writeWithdrawalOrError(wpd, res, req.RequestURI)
-}
-
-// Get status of withdrawal associated with the given WOPID
-//
-// Parameters:
-//   - long_poll_ms (optional):
-//     milliseconds to wait for state to change
-//     given old_state until responding
-//   - old_state (optional):
-//     Default is 'pending'
-func handleWithdrawalStatus(res http.ResponseWriter, req *http.Request) {
-
-       // read and validate request query parameters
-       shouldStartLongPoll := true
-       longPollMilli := DEFAULT_LONG_POLL_MS
-       if longPollMilliPtr, accepted := AcceptOptionalParamOrWriteResponse(
-               "long_poll_ms", strconv.Atoi, req, res,
-       ); accepted {
-               if longPollMilliPtr != nil {
-                       longPollMilli = *longPollMilliPtr
-               } else {
-                       // this means parameter was not given.
-                       // no long polling (simple get)
-                       shouldStartLongPoll = false
-               }
-       } else {
-               shouldStartLongPoll = false
-       }
-
-       // read and validate the wopid path parameter
-       wopid := req.PathValue(WOPID_PARAMETER)
-       wpd, err := ParseWopid(wopid)
-       if err != nil {
-               LogWarn("bank-integration-api", "wopid "+wopid+" not valid")
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_INVALID_PATH_PARAMETER",
-                       Title:    "invalid request path parameter",
-                       Detail:   "the withdrawal status request path parameter 
'wopid' is malformed",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       if shouldStartLongPoll {
-
-               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(
-                       channel,
-                       notifications,
-               )
-
-               if err != nil {
-                       err := WriteProblem(res, HTTP_NO_CONTENT, 
&RFC9457Problem{
-                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_LISTEN_FAILURE",
-                               Title:    "Failed setting up listener",
-                               Detail:   fmt.Sprintf("unable to start long 
polling due to %s", err.Error()),
-                               Instance: req.RequestURI,
-                       })
-                       if err != nil {
-                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                       }
-                       return
-               }
-
-               go listenFunc(timeoutCtx)
-
-               // go DB.ListenForWithdrawalStatusChange(timeoutCtx, 
WithdrawalIdentifier(base64.StdEncoding.EncodeToString(wpd)), statusChannel, 
errChan)
-               for {
-                       select {
-                       case <-timeoutCtx.Done():
-                               err := WriteProblem(res, HTTP_NO_CONTENT, 
&RFC9457Problem{
-                                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_LONG_POLL_TIME_EXCEEDED",
-                                       Title:    "time exceeded",
-                                       Detail:   fmt.Sprintf("long poll ended 
due to timeout: %dms", longPollMilli),
-                                       Instance: req.RequestURI,
-                               })
-                               if err != nil {
-                                       
res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                               }
-                               return
-                       case <-notifications:
-                               writeWithdrawalOrError(wpd, res, req.RequestURI)
-                               return
-                       }
-               }
-       }
-
-       writeWithdrawalOrError(wpd, res, req.RequestURI)
-}
-
-func handlePaymentNotification(res http.ResponseWriter, req *http.Request) {
-
-       wopid := req.PathValue(WOPID_PARAMETER)
-       wpd, err := ParseWopid(wopid)
-       if err != nil {
-               LogWarn("bank-integration-api", "wopid "+wopid+" not valid")
-               if wopid == "" {
-                       err := WriteProblem(res, HTTP_BAD_REQUEST, 
&RFC9457Problem{
-                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_INVALID_PATH_PARAMETER",
-                               Title:    "invalid request path parameter",
-                               Detail:   "the withdrawal status request path 
parameter 'wopid' is malformed",
-                               Instance: req.RequestURI,
-                       })
-                       if err != nil {
-                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                       }
-                       return
-               }
-       }
-
-       jsonCodec := NewJsonCodec[C2ECPaymentNotification]()
-       paymentNotification, err := ReadStructFromBody(req, jsonCodec)
-       if err != nil {
-               LogWarn("bank-integration-api", fmt.Sprintf("invalid body for 
payment notification error=%s", err.Error()))
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAW_REGISTRATION_INVALID_REQ",
-                       Title:    "invalid request",
-                       Detail:   "the payment notification request for the 
withdrawal is malformed (error: " + err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       LogInfo("bank-integration-api", "received payment notification")
-
-       err = DB.NotifyPayment(
-               wpd,
-               paymentNotification.ProviderTransactionId,
-               paymentNotification.TerminalId,
-               paymentNotification.Amount,
-               paymentNotification.Fees,
-       )
-       if err != nil {
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_PAYMENT_NOTIFICATION_FAILED",
-                       Title:    "payment notification failed",
-                       Detail:   "the payment notification failed during the 
processing of the message: " + err.Error(),
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       res.WriteHeader(HTTP_NO_CONTENT)
-}
-
-func handleWithdrawalAbort(res http.ResponseWriter, req *http.Request) {
-
-       res.WriteHeader(HTTP_OK)
-       res.Write(bytes.NewBufferString("retrieved withdrawal operation 
abortion request").Bytes())
-}
-
-// Tries to load a WithdrawalOperationStatus from the database. If no
-// entry could been found, it will write the correct error to the response.
-func writeWithdrawalOrError(wopid []byte, res http.ResponseWriter, reqUri 
string) {
-       // read the withdrawal from the database
-       withdrawal, err := DB.GetWithdrawalByWopid(wopid)
-       if err != nil {
-
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_DB_FAILURE",
-                       Title:    "database failure",
-                       Detail:   "db failure while requesting withdrawal 
(error=" + err.Error() + ")",
-                       Instance: reqUri,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       if withdrawal == nil {
-               // not found -> 404
-               err := WriteProblem(res, HTTP_NOT_FOUND, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_NOT_FOUND",
-                       Title:    "Not Found",
-                       Detail:   "No withdrawal with wopid=" + 
talerBase32Encode(wopid) + " could been found.",
-                       Instance: reqUri,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       }
-
-       // return the C2ECWithdrawalStatus
-       if amount, err := ToAmount(withdrawal.Amount); err != nil {
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_CONVERSION_FAILURE",
-                       Title:    "conversion failure",
-                       Detail:   "failed converting amount object (error:" + 
err.Error() + ")",
-                       Instance: reqUri,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
-               return
-       } else {
-               withdrawalStatusBytes, err := 
NewJsonCodec[C2ECWithdrawalStatus]().EncodeToBytes(&C2ECWithdrawalStatus{
-                       Status: withdrawal.WithdrawalStatus,
-                       Amount: *amount,
-               })
-               if err != nil {
-                       err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_CONVERSION_FAILURE",
-                               Title:    "conversion failure",
-                               Detail:   "failed converting 
C2ECWithdrawalStatus object (error:" + err.Error() + ")",
-                               Instance: reqUri,
-                       })
-                       if err != nil {
-                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-                       }
-                       return
-               }
-               res.WriteHeader(HTTP_OK)
-               res.Write(withdrawalStatusBytes)
-       }
-}
diff --git a/c2ec/postgres.go b/c2ec/db-postgres.go
similarity index 88%
rename from c2ec/postgres.go
rename to c2ec/db-postgres.go
index e9a085a..bff4ead 100644
--- a/c2ec/postgres.go
+++ b/c2ec/db-postgres.go
@@ -16,21 +16,27 @@ import (
 )
 
 const PS_INSERT_WITHDRAWAL = "INSERT INTO " + WITHDRAWAL_TABLE_NAME + " (" +
-       WITHDRAWAL_FIELD_NAME_WOPID + "," +
+       WITHDRAWAL_FIELD_NAME_WOPID + ", " + WITHDRAWAL_FIELD_NAME_RUID + ", " +
+       WITHDRAWAL_FIELD_NAME_SUGGESTED_AMOUNT + ", " + 
WITHDRAWAL_FIELD_NAME_AMOUNT + ", " +
+       WITHDRAWAL_FIELD_NAME_TRANSACTION_ID + ", " + 
WITHDRAWAL_FIELD_NAME_FEES + ", " + WITHDRAWAL_FIELD_NAME_TS +
+       ") VALUES ($1,$2,($3,$4,$5),($6,$7,$8),$9,($10,$11,$12),$13)"
+
+const PS_REGISTER_WITHDRAWAL_PARAMS = "UPDATE " + WITHDRAWAL_TABLE_NAME + " 
SET (" +
        WITHDRAWAL_FIELD_NAME_RESPUBKEY + "," +
        WITHDRAWAL_FIELD_NAME_STATUS + "," +
        WITHDRAWAL_FIELD_NAME_TS + ")" +
-       " VALUES ($1, $2, $3, $4);"
+       " = ($1,$2,$3)" +
+       " WHERE " + WITHDRAWAL_FIELD_NAME_WOPID + "=$4"
 
 const PS_GET_UNCONFIRMED_WITHDRAWALS = "SELECT * FROM " + 
WITHDRAWAL_TABLE_NAME +
        " WHERE " + WITHDRAWAL_FIELD_NAME_TRANSACTION_ID + " IS NOT NULL" +
        " AND " + WITHDRAWAL_FIELD_NAME_STATUS + " = '" + string(SELECTED) + "'"
 
 const PS_PAYMENT_NOTIFICATION = "UPDATE " + WITHDRAWAL_TABLE_NAME + " SET (" +
-       WITHDRAWAL_FIELD_NAME_AMOUNT + "," + WITHDRAWAL_FIELD_NAME_FEES + "," +
-       WITHDRAWAL_FIELD_NAME_TRANSACTION_ID + "," + 
WITHDRAWAL_FIELD_NAME_TERMINAL_ID + ")" +
-       " = (($1, $2, $3),($4, $5, $6),$7, $8)" +
-       " WHERE " + WITHDRAWAL_FIELD_NAME_WOPID + "=$9"
+       WITHDRAWAL_FIELD_NAME_FEES + "," + WITHDRAWAL_FIELD_NAME_TRANSACTION_ID 
+ "," +
+       WITHDRAWAL_FIELD_NAME_TERMINAL_ID + ")" +
+       " = (($1,$2,$3),$4,$5)" +
+       " WHERE " + WITHDRAWAL_FIELD_NAME_WOPID + "=$6"
 
 const PS_FINALISE_PAYMENT = "UPDATE " + WITHDRAWAL_TABLE_NAME + " SET (" +
        WITHDRAWAL_FIELD_NAME_STATUS + "," +
@@ -58,6 +64,9 @@ const PS_CONFIRMED_TRANSACTIONS_DESC = "SELECT * FROM " + 
WITHDRAWAL_TABLE_NAME
        " LIMIT $1" +
        " OFFSET $2"
 
+const PS_GET_WITHDRAWAL_BY_RUID = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
+       " WHERE " + WITHDRAWAL_FIELD_NAME_RUID + "=$1"
+
 const PS_GET_WITHDRAWAL_BY_ID = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
        " WHERE " + WITHDRAWAL_FIELD_NAME_ID + "=$1"
 
@@ -161,7 +170,43 @@ func (db *C2ECPostgres) registerCustomTypesHook(ctx 
context.Context, conn *pgx.C
        return nil
 }
 
-func (db *C2ECPostgres) RegisterWithdrawal(
+func (db *C2ECPostgres) SetupWithdrawal(
+       wopid []byte,
+       suggestedAmount Amount,
+       amount Amount,
+       providerTransactionId string,
+       terminalFees Amount,
+       requestUid string,
+) error {
+
+       ts := time.Now()
+       res, err := db.pool.Exec(
+               db.ctx,
+               PS_INSERT_WITHDRAWAL,
+               wopid,
+               requestUid,
+               suggestedAmount.Value,
+               suggestedAmount.Fraction,
+               suggestedAmount.Currency,
+               amount.Value,
+               amount.Fraction,
+               amount.Currency,
+               providerTransactionId,
+               terminalFees.Value,
+               terminalFees.Fraction,
+               terminalFees.Currency,
+               ts.Unix(),
+       )
+       if err != nil {
+               LogError("postgres", err)
+               return err
+       }
+       LogInfo("postgres", "query="+PS_INSERT_WITHDRAWAL)
+       LogInfo("postgres", "setup withdrawal successfully. affected 
rows="+strconv.Itoa(int(res.RowsAffected())))
+       return nil
+}
+
+func (db *C2ECPostgres) RegisterWithdrawalParameters(
        wopid []byte,
        resPubKey EddsaPublicKey,
 ) error {
@@ -174,21 +219,47 @@ func (db *C2ECPostgres) RegisterWithdrawal(
        ts := time.Now()
        res, err := db.pool.Exec(
                db.ctx,
-               PS_INSERT_WITHDRAWAL,
-               wopid,
+               PS_REGISTER_WITHDRAWAL_PARAMS,
                resPubKeyBytes,
                SELECTED,
                ts.Unix(),
+               wopid,
        )
        if err != nil {
                LogError("postgres", err)
                return err
        }
-       LogInfo("postgres", "query="+PS_INSERT_WITHDRAWAL)
+       LogInfo("postgres", "query="+PS_REGISTER_WITHDRAWAL_PARAMS)
        LogInfo("postgres", "registered withdrawal successfully. affected 
rows="+strconv.Itoa(int(res.RowsAffected())))
        return nil
 }
 
+func (db *C2ECPostgres) GetWithdrawalByRequestUid(requestUid string) 
(*Withdrawal, error) {
+
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_WITHDRAWAL_BY_RUID,
+               requestUid,
+       ); err != nil {
+               LogError("postgres", err)
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+               defer row.Close()
+               LogInfo("postgres", "query="+PS_GET_WITHDRAWAL_BY_RUID)
+               collected, err := pgx.CollectOneRow(row, 
pgx.RowToAddrOfStructByName[Withdrawal])
+               if err != nil {
+                       if errors.Is(err, pgx.ErrNoRows) {
+                               return nil, nil
+                       }
+                       return nil, err
+               }
+               return collected, nil
+       }
+}
+
 func (db *C2ECPostgres) GetWithdrawalById(withdrawalId int) (*Withdrawal, 
error) {
 
        if row, err := db.pool.Query(
@@ -252,16 +323,12 @@ func (db *C2ECPostgres) NotifyPayment(
        wopid []byte,
        providerTransactionId string,
        terminalId int,
-       amount Amount,
        fees Amount,
 ) error {
 
        res, err := db.pool.Exec(
                db.ctx,
                PS_PAYMENT_NOTIFICATION,
-               amount.Value,
-               amount.Fraction,
-               amount.Currency,
                fees.Value,
                fees.Fraction,
                fees.Currency,
diff --git a/c2ec/db.go b/c2ec/db.go
index b48d88f..ee975e0 100644
--- a/c2ec/db.go
+++ b/c2ec/db.go
@@ -19,12 +19,14 @@ const TERMINAL_FIELD_NAME_DESCRIPTION = "description"
 const TERMINAL_FIELD_NAME_PROVIDER_ID = "provider_id"
 
 const WITHDRAWAL_TABLE_NAME = "c2ec.withdrawal"
-const WITHDRAWAL_FIELD_NAME_ID = "withdrawal_id"
+const WITHDRAWAL_FIELD_NAME_ID = "withdrawal_row_id"
+const WITHDRAWAL_FIELD_NAME_RUID = "request_uid"
 const WITHDRAWAL_FIELD_NAME_WOPID = "wopid"
 const WITHDRAWAL_FIELD_NAME_RESPUBKEY = "reserve_pub_key"
 const WITHDRAWAL_FIELD_NAME_TS = "registration_ts"
 const WITHDRAWAL_FIELD_NAME_AMOUNT = "amount"
-const WITHDRAWAL_FIELD_NAME_FEES = "fees"
+const WITHDRAWAL_FIELD_NAME_SUGGESTED_AMOUNT = "suggested_amount"
+const WITHDRAWAL_FIELD_NAME_FEES = "terminal_fees"
 const WITHDRAWAL_FIELD_NAME_STATUS = "withdrawal_status"
 const WITHDRAWAL_FIELD_NAME_TERMINAL_ID = "terminal_id"
 const WITHDRAWAL_FIELD_NAME_TRANSACTION_ID = "provider_transaction_id"
@@ -60,12 +62,14 @@ type Terminal struct {
 }
 
 type Withdrawal struct {
-       WithdrawalId          uint64                    `db:"withdrawal_id"`
+       WithdrawalRowId       uint64                    `db:"withdrawal_row_id"`
+       RequestUid            string                    `db:"request_uid"`
        Wopid                 []byte                    `db:"wopid"`
        ReservePubKey         []byte                    `db:"reserve_pub_key"`
        RegistrationTs        int64                     `db:"registration_ts"`
        Amount                *TalerAmountCurrency      `db:"amount" 
scan:"follow"`
-       Fees                  *TalerAmountCurrency      `db:"fees" 
scan:"follow"`
+       SuggestedAmount       *TalerAmountCurrency      `db:"suggested_amount" 
scan:"follow"`
+       TerminalFees          *TalerAmountCurrency      `db:"terminal_fees" 
scan:"follow"`
        WithdrawalStatus      WithdrawalOperationStatus `db:"withdrawal_status"`
        TerminalId            *int64                    `db:"terminal_id"`
        ProviderTransactionId *string                   
`db:"provider_transaction_id"`
@@ -101,13 +105,28 @@ type Notification struct {
 // C2EC compliant database interface must implement
 // in order to be bound to the c2ec API.
 type C2ECDatabase interface {
-       // Registers a wopid and reserve public key.
+       // A terminal sets up a withdrawal
+       // with this query.
        // This initiates the withdrawal.
-       RegisterWithdrawal(
+       SetupWithdrawal(
+               wopid []byte,
+               suggestedAmount Amount,
+               amount Amount,
+               providerTransactionId string,
+               terminalFees Amount,
+               requestUid string,
+       ) error
+
+       // Registers a reserve public key
+       // belonging to the respective wopid.
+       RegisterWithdrawalParameters(
                wopid []byte,
                resPubKey EddsaPublicKey,
        ) error
 
+       // Get the withdrawal associated with the given request uid.
+       GetWithdrawalByRequestUid(requestUid string) (*Withdrawal, error)
+
        // Get the withdrawal associated with the given withdrawal identifier.
        GetWithdrawalById(withdrawalId int) (*Withdrawal, error)
 
@@ -124,7 +143,6 @@ type C2ECDatabase interface {
                wopid []byte,
                providerTransactionId string,
                terminalId int,
-               amount Amount,
                fees Amount,
        ) error
 
diff --git a/c2ec/db/0001-c2ec_schema.sql b/c2ec/db/0001-c2ec_schema.sql
index 7fbfa60..633f5c5 100644
--- a/c2ec/db/0001-c2ec_schema.sql
+++ b/c2ec/db/0001-c2ec_schema.sql
@@ -75,12 +75,14 @@ COMMENT ON COLUMN terminal.provider_id
 
 
 CREATE TABLE IF NOT EXISTS withdrawal (
-    withdrawal_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    withdrawal_row_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    request_uid TEXT UNIQUE NOT NULL,
     wopid BYTEA CHECK (LENGTH(wopid)=32) UNIQUE NOT NULL,
-    reserve_pub_key BYTEA CHECK (LENGTH(reserve_pub_key)=32) NOT NULL,
+    reserve_pub_key BYTEA CHECK (LENGTH(reserve_pub_key)=32),
     registration_ts INT8 NOT NULL,
     amount taler_amount_currency,
-    fees taler_amount_currency,
+    suggested_amount taler_amount_currency,
+    terminal_fees taler_amount_currency,
     withdrawal_status withdrawal_operation_status NOT NULL DEFAULT 'pending',
     terminal_id INT8 REFERENCES terminal(terminal_id),
     provider_transaction_id TEXT,
@@ -90,7 +92,10 @@ CREATE TABLE IF NOT EXISTS withdrawal (
 );
 COMMENT ON TABLE withdrawal
   IS 'Table representing withdrawal processes initiated by terminals';
-COMMENT ON COLUMN withdrawal.withdrawal_id
+COMMENT ON COLUMN withdrawal.request_uid 
+  IS 'The request uid identifies each request and is stored to make the API 
interacting
+  with withdrawals idempotent.';
+COMMENT ON COLUMN withdrawal.withdrawal_row_id
   IS 'The withdrawal id is used a technical id used by the wire gateway to 
sequentially select new transactions';
 COMMENT ON COLUMN withdrawal.wopid
   IS 'The wopid (withdrawal operation id) is a nonce generated by the terminal 
requesting a withdrawal.
@@ -101,8 +106,11 @@ COMMENT ON COLUMN withdrawal.registration_ts
   IS 'Timestamp of when the withdrawal request was registered';
 COMMENT ON COLUMN withdrawal.amount
   IS 'Effective amount to be put into the reserve after completion';
-COMMENT ON COLUMN withdrawal.fees
-  IS 'Fees associated with the withdrawal, including exchange and provider 
fees';
+COMMENT ON COLUMN withdrawal.suggested_amount
+  IS 'The suggested amount is given by the entity initializing the wihdrawal.
+  If the suggested amount is given, the wallet may still change the amount.';
+COMMENT ON COLUMN withdrawal.terminal_fees
+  IS 'Fees associated with the withdrawal but not related to the taler payment 
system.';
 COMMENT ON COLUMN withdrawal.withdrawal_status
   IS 'Status of the withdrawal process';
 COMMENT ON COLUMN withdrawal.terminal_id
diff --git a/c2ec/db/drop.sql b/c2ec/db/drop.sql
index 042a9c4..3694296 100644
--- a/c2ec/db/drop.sql
+++ b/c2ec/db/drop.sql
@@ -3,4 +3,6 @@ BEGIN;
 
 DROP SCHEMA IF EXISTS c2ec CASCADE;
 
+DROP SCHEMA IF EXISTS _v CASCADE;
+
 COMMIT;
\ No newline at end of file
diff --git a/c2ec/db/proc-c2ec_payment_notification_listener.sql 
b/c2ec/db/proc-c2ec_payment_notification_listener.sql
index c9168b6..f6c54c4 100644
--- a/c2ec/db/proc-c2ec_payment_notification_listener.sql
+++ b/c2ec/db/proc-c2ec_payment_notification_listener.sql
@@ -15,10 +15,10 @@ BEGIN
         ON t.provider_id = p.provider_id
         LEFT JOIN c2ec.withdrawal AS w
         ON t.terminal_id = NEW.terminal_id
-        WHERE w.withdrawal_id = NEW.withdrawal_id;
+        WHERE w.withdrawal_row_id = NEW.withdrawal_row_id;
     PERFORM pg_notify('payment_notification',
         provider_name || '|' ||
-        NEW.withdrawal_id || '|' || 
+        NEW.withdrawal_row_id || '|' || 
         NEW.provider_transaction_id
     );
     RETURN NULL;
diff --git a/c2ec/db/proc-c2ec_retry_listener.sql 
b/c2ec/db/proc-c2ec_retry_listener.sql
index 801735f..bcaad85 100644
--- a/c2ec/db/proc-c2ec_retry_listener.sql
+++ b/c2ec/db/proc-c2ec_retry_listener.sql
@@ -8,7 +8,7 @@ SET search_path TO c2ec;
 CREATE OR REPLACE FUNCTION emit_retry_notification() 
 RETURNS TRIGGER AS $$
 BEGIN
-    PERFORM pg_notify('retry', '' || NEW.withdrawal_id);
+    PERFORM pg_notify('retry', '' || NEW.withdrawal_row_id);
     RETURN NULL;
 END;
 $$ LANGUAGE plpgsql;
diff --git a/c2ec/db/proc-c2ec_status_listener.sql 
b/c2ec/db/proc-c2ec_status_listener.sql
index d40f5c8..38b10a0 100644
--- a/c2ec/db/proc-c2ec_status_listener.sql
+++ b/c2ec/db/proc-c2ec_status_listener.sql
@@ -28,13 +28,13 @@ COMMENT ON TRIGGER c2ec_withdrawal_created ON withdrawal
     IS 'After creation of the withdrawal entry a notification shall 
     be triggered using this trigger.';
 
-CREATE OR REPLACE TRIGGER c2ec_withdrawal_changed 
+CREATE OR REPLACE TRIGGER c2ec_withdrawal_status_changed 
     AFTER UPDATE OF withdrawal_status
     ON withdrawal
     FOR EACH ROW
     WHEN (OLD.withdrawal_status IS DISTINCT FROM NEW.withdrawal_status)
     EXECUTE FUNCTION emit_withdrawal_status();
-COMMENT ON TRIGGER c2ec_withdrawal_changed ON withdrawal
+COMMENT ON TRIGGER c2ec_withdrawal_status_changed ON withdrawal
     IS 'After the update of the status (only the status is of interest) 
     a notification shall be triggered using this trigger.';
 
diff --git a/c2ec/db/procedures.sql b/c2ec/db/procedures.sql
deleted file mode 100644
index 50ff87f..0000000
--- a/c2ec/db/procedures.sql
+++ /dev/null
@@ -1,162 +0,0 @@
-BEGIN;
-
-SELECT _v.register_patch('proc-c2ec-status-listener', 
ARRAY['0001-c2ec-schema'], NULL);
-
-SET search_path TO c2ec;
-
--- to create a function, the user needs USAGE privilege on arguments and 
return types
-CREATE OR REPLACE FUNCTION emit_withdrawal_status() 
-RETURNS TRIGGER AS $$
-BEGIN
-    PERFORM pg_notify('w_' || encode(NEW.wopid::BYTEA, 'base64'), 
NEW.withdrawal_status::TEXT);
-    RETURN NULL;
-END;
-$$ LANGUAGE plpgsql;
-COMMENT ON FUNCTION emit_withdrawal_status 
-       IS 'The function encodes the wopid in base64 and 
-    sends a notification on the channel "w_{wopid}" 
-    with the status in the payload.';
-
--- for creating a trigger the user must have TRIGGER pivilege on the table.
--- to execute the trigger, the user needs EXECUTE privilege on the trigger 
function.
-CREATE OR REPLACE TRIGGER c2ec_withdrawal_created 
-    AFTER INSERT
-    ON withdrawal
-    FOR EACH ROW
-    EXECUTE FUNCTION emit_withdrawal_status();
-COMMENT ON TRIGGER c2ec_withdrawal_created ON withdrawal
-    IS 'After creation of the withdrawal entry a notification shall 
-    be triggered using this trigger.';
-
-CREATE OR REPLACE TRIGGER c2ec_withdrawal_changed 
-    AFTER UPDATE OF withdrawal_status
-    ON withdrawal
-    FOR EACH ROW
-    WHEN (OLD.withdrawal_status IS DISTINCT FROM NEW.withdrawal_status)
-    EXECUTE FUNCTION emit_withdrawal_status();
-COMMENT ON TRIGGER c2ec_withdrawal_changed ON withdrawal
-    IS 'After the update of the status (only the status is of interest) 
-    a notification shall be triggered using this trigger.';
-
-COMMIT;
-
-BEGIN;
-
-SELECT _v.register_patch('proc-c2ec-retry-listener', 
ARRAY['0001-c2ec-schema'], NULL);
-
-SET search_path TO c2ec;
-
--- to create a function, the user needs USAGE privilege on arguments and 
return types
-CREATE OR REPLACE FUNCTION emit_retry_notification() 
-RETURNS TRIGGER AS $$
-BEGIN
-    PERFORM pg_notify('retry', '' || NEW.withdrawal_id);
-    RETURN NULL;
-END;
-$$ LANGUAGE plpgsql;
-COMMENT ON FUNCTION emit_retry_notification 
-       IS 'The function emits the id of the withdrawal for which the last 
-    retry timestamp was updated. This shall trigger a retry operation.
-    How many retries are attempted is specified and handled by the 
application';
-
--- for creating a trigger the user must have TRIGGER pivilege on the table.
--- to execute the trigger, the user needs EXECUTE privilege on the trigger 
function.
-CREATE OR REPLACE TRIGGER c2ec_retry_notify 
-    AFTER UPDATE OF last_retry_ts
-    ON withdrawal
-    FOR EACH ROW
-    EXECUTE FUNCTION emit_retry_notification();
-COMMENT ON TRIGGER c2ec_retry_notify ON withdrawal
-    IS 'After setting the last retry timestamp on the withdrawal,
-    trigger the retry mechanism through the respective mechanism.';
-
-COMMIT;
-
-BEGIN;
-
-SELECT _v.register_patch('proc-c2ec-payment-notification-listener', 
ARRAY['0001-c2ec-schema'], NULL);
-
-SET search_path TO c2ec;
-
--- to create a function, the user needs USAGE privilege on arguments and 
return types
-CREATE OR REPLACE FUNCTION emit_payment_notification() 
-RETURNS TRIGGER AS $$
-DECLARE
-    provider_name TEXT;
-BEGIN
-    SELECT p.name INTO provider_name FROM c2ec.provider AS p 
-        LEFT JOIN c2ec.terminal AS t 
-        ON t.provider_id = p.provider_id
-        LEFT JOIN c2ec.withdrawal AS w
-        ON t.terminal_id = w.terminal_id
-        WHERE w.withdrawal_id = NEW.withdrawal_id;
-    PERFORM pg_notify('payment_notification',
-        provider_name || '|' ||
-        NEW.withdrawal_id || '|' || 
-        NEW.provider_transaction_id
-    );
-    RETURN NULL;
-END;
-$$ LANGUAGE plpgsql;
-COMMENT ON FUNCTION emit_payment_notification 
-       IS 'The function emits the name of the provider, row id of the 
withdrawal
-    and the provider_transaction_id, on the channel "payment_notification".
-    The format of the payload is as follows: 
-    "{PROVIDER_NAME}|{WITHDRAWAL_ID}|{PROVIDER_TRANSACTION_ID}". The subscriber
-    shall decide which attestation process to use, based on the name of 
-    the provider.';
-
--- for creating a trigger the user must have TRIGGER pivilege on the table.
--- to execute the trigger, the user needs EXECUTE privilege on the trigger 
function.
-CREATE OR REPLACE TRIGGER c2ec_on_payment_notify 
-    AFTER UPDATE OF provider_transaction_id
-    ON withdrawal
-    FOR EACH ROW    
-    WHEN (NEW.provider_transaction_id IS NOT NULL)
-    EXECUTE FUNCTION emit_payment_notification();
-COMMENT ON TRIGGER c2ec_on_payment_notify ON withdrawal
-    IS 'After setting the provider transaction id following a payment 
notification,
-    trigger the emit to the respective channel.';
-
-COMMIT;
-
-BEGIN;
-
-SELECT _v.register_patch('proc-c2ec-transfer-listener', 
ARRAY['0001-c2ec-schema'], NULL);
-
-SET search_path TO c2ec;
-
--- to create a function, the user needs USAGE privilege on arguments and 
return types
-CREATE OR REPLACE FUNCTION emit_transfer_notification() 
-RETURNS TRIGGER AS $$
-BEGIN
-    PERFORM pg_notify('transfer', encode(NEW.request_uid::BYTEA, 'base64'));
-    RETURN NULL;
-END;
-$$ LANGUAGE plpgsql;
-COMMENT ON FUNCTION emit_transfer_notification 
-       IS 'The function emits the request_uid of a transfer which shall 
trigger a transfer
-    by the receiver of the notification.';
-
--- for creating a trigger the user must have TRIGGER pivilege on the table.
--- to execute the trigger, the user needs EXECUTE privilege on the trigger 
function.
-CREATE OR REPLACE TRIGGER c2ec_on_transfer_failed 
-    AFTER INSERT
-    ON transfer
-    FOR EACH ROW    
-    EXECUTE FUNCTION emit_transfer_notification();
-COMMENT ON TRIGGER c2ec_on_transfer_failed ON transfer
-    IS 'When a new transfer is set, the transfer shall executed. This trigger 
aims to
-    trigger this operation at its listeners.';
-
-CREATE OR REPLACE TRIGGER c2ec_on_transfer_failed 
-    AFTER UPDATE OF retries
-    ON transfer
-    FOR EACH ROW
-    WHEN (NEW.retries > 0)
-    EXECUTE FUNCTION emit_transfer_notification();
-COMMENT ON TRIGGER c2ec_on_transfer_failed ON transfer
-    IS 'When retries is (re)set this will trigger the notification of the 
listening 
-    receivers, which will further process the transfer';
-
-COMMIT;
\ No newline at end of file
diff --git a/c2ec/db/procedures.sql.in b/c2ec/db/procedures.sql.in
deleted file mode 100644
index 1bdd666..0000000
--- a/c2ec/db/procedures.sql.in
+++ /dev/null
@@ -1 +0,0 @@
-Note: cat into procedures.sql
\ No newline at end of file
diff --git a/c2ec/db/test_c2ec_test.sql b/c2ec/db/test_c2ec_simulation.sql
similarity index 100%
rename from c2ec/db/test_c2ec_test.sql
rename to c2ec/db/test_c2ec_simulation.sql
diff --git a/c2ec/db/test_c2ec_test_rollback.sql 
b/c2ec/db/test_c2ec_simulation_rollback.sql
similarity index 100%
rename from c2ec/db/test_c2ec_test_rollback.sql
rename to c2ec/db/test_c2ec_simulation_rollback.sql
diff --git a/c2ec/encoding.go b/c2ec/encoding.go
index e00a8f4..59fec08 100644
--- a/c2ec/encoding.go
+++ b/c2ec/encoding.go
@@ -1,43 +1,24 @@
 package main
 
 import (
-       "encoding/base32"
        "errors"
-       "net/url"
+       "math"
        "strings"
 )
 
-// 32 characters for decoding, using RFC 3548.
-const TALER_BASE32_CHARACTER_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
+func talerBinaryEncode(byts []byte) string {
 
-func talerBase32Encode(byts []byte) string {
-       return talerBase32Encoding().EncodeToString(byts)
+       return encodeCrock(byts)
 }
 
-func talerBase32Decode(str string) ([]byte, error) {
+func talerBinaryDecode(str string) ([]byte, error) {
 
-       decoded, err := talerBase32Encoding().DecodeString(strings.ToUpper(str))
-       if err != nil {
-               return nil, err
-       }
-       return decoded, nil
-}
-
-func talerBase32Encoding() *base32.Encoding {
-       // 32 characters for decoding, using RFC 3548.
-       // character set copied from 
[TALER-EXCHANGE]/src/util/crypto_confirmation.c
-       return base32.NewEncoding(TALER_BASE32_CHARACTER_SET)
+       return decodeCrock(str)
 }
 
 func ParseWopid(wopid string) ([]byte, error) {
 
-       unescaped, err := url.PathUnescape(wopid)
-       if err != nil {
-               LogError("encoding", err)
-               return nil, errors.New("decoding failed")
-       }
-
-       wopidBytes, err := talerBase32Decode(unescaped)
+       wopidBytes, err := talerBinaryDecode(wopid)
        if err != nil {
                return nil, err
        }
@@ -53,15 +34,105 @@ func ParseWopid(wopid string) ([]byte, error) {
 
 func FormatWopid(wopid []byte) string {
 
-       return url.PathEscape(talerBase32Encode(wopid))
+       return talerBinaryEncode(wopid)
 }
 
 func ParseEddsaPubKey(key EddsaPublicKey) ([]byte, error) {
 
-       return talerBase32Decode(string(key))
+       return talerBinaryDecode(string(key))
 }
 
 func FormatEddsaPubKey(key []byte) EddsaPublicKey {
 
-       return EddsaPublicKey(talerBase32Encode(key))
+       return EddsaPublicKey(talerBinaryEncode(key))
+}
+
+func decodeCrock(e string) ([]byte, error) {
+       size := len(e)
+       bitpos := 0
+       bitbuf := 0
+       readPosition := 0
+       outLen := int(math.Floor((float64(size) * 5.0) / 8.0))
+       out := make([]byte, outLen)
+       outPos := 0
+
+       getValue := func(c byte) (int, error) {
+               alphabet := "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
+               switch c {
+               case 'o', 'O':
+                       return 0, nil
+               case 'i', 'I', 'l', 'L':
+                       return 1, nil
+               case 'u', 'U':
+                       return 27, nil
+               }
+
+               i := strings.IndexRune(alphabet, rune(c))
+               if i > -1 && i < 32 {
+                       return i, nil
+               }
+
+               return -1, errors.New("encoding error")
+       }
+
+       for readPosition < size || bitpos > 0 {
+               if readPosition < size {
+                       v, err := getValue(e[readPosition])
+                       if err != nil {
+                               return nil, err
+                       }
+                       readPosition++
+                       bitbuf = bitbuf<<5 | v
+                       bitpos += 5
+               }
+               for bitpos >= 8 {
+                       d := byte(bitbuf >> (bitpos - 8) & 0xff)
+                       out[outPos] = d
+                       outPos++
+                       bitpos -= 8
+               }
+               if readPosition == size && bitpos > 0 {
+                       bitbuf = bitbuf << (8 - bitpos) & 0xff
+                       if bitbuf == 0 {
+                               bitpos = 0
+                       } else {
+                               bitpos = 8
+                       }
+               }
+       }
+       return out, nil
+}
+
+func encodeCrock(data []byte) string {
+       out := ""
+       bitbuf := 0
+       bitpos := 0
+
+       encodeValue := func(value int) byte {
+               alphabet := "ABCDEFGHJKMNPQRSTVWXYZ"
+               switch {
+               case value >= 0 && value <= 9:
+                       return byte('0' + value)
+               case value >= 10 && value <= 31:
+                       return alphabet[value-10]
+               default:
+                       panic("Invalid value for encoding")
+               }
+       }
+
+       for _, b := range data {
+               bitbuf = bitbuf<<8 | int(b&0xff)
+               bitpos += 8
+               for bitpos >= 5 {
+                       value := bitbuf >> (bitpos - 5) & 0x1f
+                       out += string(encodeValue(value))
+                       bitpos -= 5
+               }
+       }
+       if bitpos > 0 {
+               bitbuf = bitbuf << (5 - bitpos)
+               value := bitbuf & 0x1f
+               out += string(encodeValue(value))
+       }
+       return out
 }
diff --git a/c2ec/encoding_test.go b/c2ec/encoding_test.go
index 1b9dbd0..d9ad0e7 100644
--- a/c2ec/encoding_test.go
+++ b/c2ec/encoding_test.go
@@ -41,9 +41,9 @@ func TestTalerBase32(t *testing.T) {
        input := []byte("This is some text")
        t.Log("in:", string(input))
        t.Log("in:", input)
-       encoded := talerBase32Encode(input)
+       encoded := talerBinaryEncode(input)
        t.Log("encoded:", encoded)
-       out, err := talerBase32Decode(encoded)
+       out, err := talerBinaryDecode(encoded)
        if err != nil {
                t.Error(err)
                t.FailNow()
@@ -75,9 +75,9 @@ func TestTalerBase32Rand32(t *testing.T) {
        }
 
        t.Log("in:", input)
-       encoded := talerBase32Encode(input)
+       encoded := talerBinaryEncode(input)
        t.Log("encoded:", encoded)
-       out, err := talerBase32Decode(encoded)
+       out, err := talerBinaryDecode(encoded)
        if err != nil {
                t.Error(err)
                t.FailNow()
@@ -109,9 +109,9 @@ func TestTalerBase32Rand64(t *testing.T) {
        }
 
        t.Log("in:", input)
-       encoded := talerBase32Encode(input)
+       encoded := talerBinaryEncode(input)
        t.Log("encoded:", encoded)
-       out, err := talerBase32Decode(encoded)
+       out, err := talerBinaryDecode(encoded)
        if err != nil {
                t.Error(err)
                t.FailNow()
diff --git a/c2ec/http-util.go b/c2ec/http-util.go
index f753050..bb91d1e 100644
--- a/c2ec/http-util.go
+++ b/c2ec/http-util.go
@@ -20,31 +20,6 @@ const HTTP_METHOD_NOT_ALLOWED = 405
 const HTTP_CONFLICT = 409
 const HTTP_INTERNAL_SERVER_ERROR = 500
 
-const TALER_URI_PROBLEM_PREFIX = "taler://problem"
-
-type RFC9457Problem struct {
-       TypeUri  string `json:"type"`
-       Title    string `json:"title"`
-       Detail   string `json:"detail"`
-       Instance string `json:"instance"`
-}
-
-// Writes a problem as specified by RFC 9457 to
-// the response. The problem is always serialized
-// as JSON.
-func WriteProblem(res http.ResponseWriter, status int, problem 
*RFC9457Problem) error {
-
-       c := NewJsonCodec[RFC9457Problem]()
-       problm, err := c.EncodeToBytes(problem)
-       if err != nil {
-               return err
-       }
-
-       res.WriteHeader(status)
-       res.Write(problm)
-       return nil
-}
-
 // Function reads and validates a param of a request in the
 // correct format according to the transform function supplied.
 // When the transform fails, it returns false as second return
@@ -61,15 +36,7 @@ func AcceptOptionalParamOrWriteResponse[T any](
 
        ptr, err := OptionalQueryParamOrError(name, transform, req)
        if err != nil {
-               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_INVALID_REQUEST_QUERY_PARAMETER",
-                       Title:    "invalid request query parameter",
-                       Detail:   "the withdrawal status request parameter '" + 
name + "' is malformed (error: " + err.Error() + ")",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               res.WriteHeader(HTTP_BAD_REQUEST)
                return nil, false
        }
 
@@ -82,15 +49,7 @@ func AcceptOptionalParamOrWriteResponse[T any](
        assertedObj, ok := any(obj).(T)
        if !ok {
                // this should generally not happen (due to the implementation)
-               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
-                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_FATAL_ERROR",
-                       Title:    "Fatal Error",
-                       Detail:   "Something strange happened. Probably not 
your fault.",
-                       Instance: req.RequestURI,
-               })
-               if err != nil {
-                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
-               }
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return nil, false
        }
        return &assertedObj, true
@@ -178,6 +137,9 @@ func HttpGet[T any](
        if codec == nil {
                return nil, res.StatusCode, err
        } else {
+               if res.StatusCode > 299 {
+                       return nil, res.StatusCode, nil
+               }
                resBody, err := codec.Decode(res.Body)
                return resBody, res.StatusCode, err
        }
diff --git a/c2ec/install/build_app.sh b/c2ec/install/build_app.sh
new file mode 100644
index 0000000..87d872f
--- /dev/null
+++ b/c2ec/install/build_app.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+if [ "$#" -ne 1 ]; then
+    echo "Usage: $0 <source-root>"
+    exit 1
+fi
+
+REPO_ROOT=$1
+
+build_c2ec() {
+    go build $REPO_ROOT
+    if [ $? -ne 0 ]; then
+        echo "Failed to build C2EC using Go"
+        exit 1
+    fi
+}
+
+build_c2ec
+if [ $? -ne 0 ]; then
+    exit 1
+fi
diff --git a/c2ec/install/installation_notes.md 
b/c2ec/install/installation_notes.md
new file mode 100644
index 0000000..8858173
--- /dev/null
+++ b/c2ec/install/installation_notes.md
@@ -0,0 +1,33 @@
+# Installation of C2EC and surrounding components
+
+how to install exchange and C2EC
+
+## Prerequisites
+
+- Debian
+- git
+- postgres >= 15.6
+- it's a good idea to read [Exchange Operator 
Manual](https://docs.taler.net/taler-exchange-manual.html) first
+
+## Required Exchange binaries
+
+To allow the withdrawal of Taler, I will need following binaries:
+
+- taler-exchange-httpd
+- taler-exchange-secmod-rsa
+- taler-exchange-secmod-cs
+- taler-exchange-secmod-eddsa
+- taler-exchange-closer
+- taler-exchange-wirewatch
+
+## 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`
+
+`sudo wget -O /etc/apt/keyrings/taler-systems.gpg 
https://taler.net/taler-systems.gpg`
+
+`sudo apt update`
+
+`sudo apt install taler-exchange`
+
+## Configure
\ No newline at end of file
diff --git a/c2ec/install/setup_db.sh b/c2ec/install/setup_db.sh
new file mode 100755
index 0000000..6038997
--- /dev/null
+++ b/c2ec/install/setup_db.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+if [ "$#" -ne 4 ]; then
+    echo "Usage: $0 <db-username> <db-password> <db-name> <source-root>"
+    exit 1
+fi
+
+DB_USERNAME=$1
+DB_PASSWORD=$2
+DB_NAME=$3
+REPO_ROOT=$4
+
+SQL_SCRIPTS=(
+    "$REPO_ROOT/db/versioning.sql"
+    "$REPO_ROOT/db/0001-c2ec_schema.sql"
+    "$REPO_ROOT/db/proc-c2ec_status_listener.sql"
+    "$REPO_ROOT/db/proc-c2ec_payment_notification_listener.sql"
+    "$REPO_ROOT/db/proc-c2ec_retry_listener.sql"
+    "$REPO_ROOT/db/proc-c2ec_transfer_listener.sql"
+)
+
+execute_sql_scripts() {
+    for script in "${SQL_SCRIPTS[@]}"; do
+        echo "Executing SQL script: $script"
+        PGPASSWORD=$DB_PASSWORD psql -U $DB_USERNAME -d $DB_NAME -f $script
+        if [ $? -ne 0 ]; then
+            echo "Failed to execute SQL script: $script"
+            exit 1
+        fi
+    done
+    PGPASSWORD=""
+}
+
+execute_sql_scripts
+if [ $? -ne 0 ]; then
+    exit 1
+fi
diff --git a/c2ec/install/start.sh b/c2ec/install/start.sh
new file mode 100755
index 0000000..031da29
--- /dev/null
+++ b/c2ec/install/start.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+if [ "$#" -ne 1 ]; then
+    echo "Usage: $0 <config-file-path>"
+    exit 1
+fi
+CONFIG_FILE=$1
+
+build_and_run_go_app() {
+    go build -o app
+    if [ $? -ne 0 ]; then
+        echo "Failed to build Go application"
+        exit 1
+    fi
+
+    ./app "$CONFIG_FILE"
+    if [ $? -ne 0 ]; then
+        echo "Failed to run Go application"
+        exit 1
+    fi
+}
+
+build_and_run_go_app
+
+rm -f app
\ No newline at end of file
diff --git a/c2ec/install/wipe_db.sh b/c2ec/install/wipe_db.sh
new file mode 100755
index 0000000..69088e9
--- /dev/null
+++ b/c2ec/install/wipe_db.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+if [ "$#" -ne 3 ]; then
+    echo "Usage: $0 <db-username> <db-password> <db-name>"
+    exit 1
+fi
+
+DB_USERNAME=$1
+DB_PASSWORD=$2
+DB_NAME=$3
+
+SQL_SCRIPTS=(
+    "./../db/drop.sql"
+)
+
+execute_sql_scripts() {
+    for script in "${SQL_SCRIPTS[@]}"; do
+        PGPASSWORD=$DB_PASSWORD psql -U $DB_USERNAME -d $DB_NAME -f "$script"
+        if [ $? -ne 0 ]; then
+            echo "Failed to execute SQL script: $script"
+            exit 1
+        fi
+    done
+    PGPASSWORD=""
+}
+
+execute_sql_scripts
diff --git a/c2ec/main.go b/c2ec/main.go
index 3eb8d98..7f1195e 100644
--- a/c2ec/main.go
+++ b/c2ec/main.go
@@ -15,8 +15,9 @@ import (
 const GET = "GET "
 const POST = "POST "
 
-const BANK_INTEGRATION_API = "/c2ec"
-const WIRE_GATEWAY_API = "/wire"
+// 
https://docs.taler.net/core/api-terminal.html#endpoints-for-integrated-sub-apis
+const BANK_INTEGRATION_API = "/taler-integration"
+const WIRE_GATEWAY_API = "/taler-wire-gateway"
 
 const DEFAULT_C2EC_CONFIG_PATH = "c2ec-config.yaml" // "c2ec-config.conf"
 
@@ -94,6 +95,8 @@ func main() {
 
        setupWireGatewayRoutes(router)
 
+       setupTerminalRoutes(router)
+
        server := http.Server{
                Handler: router,
        }
@@ -241,19 +244,14 @@ func setupBankIntegrationRoutes(router *http.ServeMux) {
                bankIntegrationConfig,
        )
 
-       router.HandleFunc(
-               POST+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_BY_WOPID_PATTERN,
-               handleWithdrawalRegistration,
-       )
-
        router.HandleFunc(
                GET+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_BY_WOPID_PATTERN,
                handleWithdrawalStatus,
        )
 
        router.HandleFunc(
-               POST+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_PAYMENT_PATTERN,
-               handlePaymentNotification,
+               POST+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_BY_WOPID_PATTERN,
+               handleParameterRegistration,
        )
 
        router.HandleFunc(
@@ -289,3 +287,26 @@ func setupWireGatewayRoutes(router *http.ServeMux) {
                adminAddIncoming,
        )
 }
+
+func setupTerminalRoutes(router *http.ServeMux) {
+
+       router.HandleFunc(
+               GET+TERMINAL_API_CONFIG,
+               handleTerminalConfig,
+       )
+
+       router.HandleFunc(
+               POST+TERMINAL_API_REGISTER_WITHDRAWAL,
+               handleWithdrawalSetup,
+       )
+
+       router.HandleFunc(
+               POST+TERMINAL_API_CHECK_WITHDRAWAL,
+               handleWithdrawalCheck,
+       )
+
+       router.HandleFunc(
+               GET+TERMINAL_API_WITHDRAWAL_STATUS,
+               handleWithdrawalStatus,
+       )
+}
diff --git a/c2ec/attestor.go b/c2ec/proc-attestor.go
similarity index 98%
rename from c2ec/attestor.go
rename to c2ec/proc-attestor.go
index fb2bda2..443f2ea 100644
--- a/c2ec/attestor.go
+++ b/c2ec/proc-attestor.go
@@ -47,7 +47,7 @@ func attestationCallback(notification *Notification, errs 
chan error) {
        }
        withdrawalRowId, err := strconv.Atoi(payload[1])
        if err != nil {
-               errs <- errors.New("malformed withdrawal_id: " + err.Error())
+               errs <- errors.New("malformed withdrawal_row_id: " + 
err.Error())
                return
        }
        providerTransactionId := payload[2]
@@ -136,7 +136,7 @@ func prepareRetryOrAbort(
 
        if withdrawal.RetryCounter >= CONFIG.Server.MaxRetries {
 
-               LogInfo("attestor", fmt.Sprintf("max retries for withdrawal 
with id=%d was reached. withdrawal is aborted.", withdrawal.WithdrawalId))
+               LogInfo("attestor", fmt.Sprintf("max retries for withdrawal 
with id=%d was reached. withdrawal is aborted.", withdrawal.WithdrawalRowId))
                err := DB.FinaliseWithdrawal(withdrawalRowId, ABORTED, 
make([]byte, 0))
                if err != nil {
                        LogError("attestor", err)
diff --git a/c2ec/listener.go b/c2ec/proc-listener.go
similarity index 100%
rename from c2ec/listener.go
rename to c2ec/proc-listener.go
diff --git a/c2ec/transfer.go b/c2ec/proc-transfer.go
similarity index 100%
rename from c2ec/transfer.go
rename to c2ec/proc-transfer.go
diff --git a/cli/cli.go b/cli/cli.go
index ea36f23..88f9b72 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -2,6 +2,7 @@ package main
 
 import (
        "bufio"
+       "bytes"
        "context"
        "crypto/rand"
        "encoding/base64"
@@ -16,6 +17,7 @@ import (
 )
 
 const ACTION_HELP = "h"
+const ACTION_SETUP_SIMULATION = "sim"
 const ACTION_REGISTER_PROVIDER = "rp"
 const ACTION_REGISTER_TERMINAL = "rt"
 const ACTION_DEACTIVATE_TERMINAL = "dt"
@@ -147,7 +149,7 @@ func registerWalleeTerminal() error {
                INSERT_TERMINAL,
                hashedAccessToken,
                description,
-               p.ProviderTerminalID,
+               p.ProviderId,
        )
        if err != nil {
                return err
@@ -176,6 +178,10 @@ func registerWalleeTerminal() error {
 
 func deactivateTerminal() error {
 
+       if DB == nil {
+               return errors.New("connect to the database first (cmd: db)")
+       }
+
        fmt.Println("You are about to deactivate terminal which allows 
withdrawals. This will make the terminal unusable.")
        tuid := read("Terminal-User-Id: ")
        parts := strings.Split(tuid, "-")
@@ -199,6 +205,94 @@ func deactivateTerminal() error {
        return nil
 }
 
+func setupSimulation() error {
+
+       if DB == nil {
+               return errors.New("connect to the database first (cmd: db)")
+       }
+
+       // SETTING UP PROVIDER
+       fmt.Println("Setting up simulation provider and terminal.")
+       name := "Simulation"
+       paytotargettype := "void"
+       backendUrl := "simulation provider will not contact any backend."
+       credsEncoded := 
base64.StdEncoding.EncodeToString(bytes.NewBufferString("simulation provider 
will not contact any backend.").Bytes())
+
+       _, err := DB.Exec(
+               context.Background(),
+               INSERT_PROVIDER,
+               name,
+               paytotargettype,
+               backendUrl,
+               credsEncoded,
+       )
+       if err != nil {
+               return err
+       }
+
+       // SETTING UP TERMINAL
+       description := "simulation terminal"
+
+       rows, err := DB.Query(
+               context.Background(),
+               GET_PROVIDER_BY_NAME,
+               name,
+       )
+       if err != nil {
+               return err
+       }
+
+       p, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByName[Provider])
+       if err != nil {
+               return err
+       }
+       rows.Close() // release rows / connection
+
+       accessToken := make([]byte, 32)
+       _, err = rand.Read(accessToken)
+       if err != nil {
+               return err
+       }
+
+       accessTokenBase64 := base64.StdEncoding.EncodeToString(accessToken)
+
+       hashedAccessToken, err := pbkdf(accessTokenBase64)
+       if err != nil {
+               return err
+       }
+
+       _, err = DB.Exec(
+               context.Background(),
+               INSERT_TERMINAL,
+               hashedAccessToken,
+               description,
+               p.ProviderId,
+       )
+       if err != nil {
+               return err
+       }
+
+       fmt.Println("looking up last inserted terminal")
+       rows, err = DB.Query(
+               context.Background(),
+               GET_LAST_INSERTED_TERMINAL,
+       )
+       if err != nil {
+               return err
+       }
+       t, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByName[Terminal])
+       if err != nil {
+               return err
+       }
+       rows.Close()
+
+       fmt.Println("Terminal-User-Id (used to identify terminal at the api. 
You want to note this):", name+"-"+strconv.Itoa(int(t.TerminalID)))
+       fmt.Println("GENERATED ACCESS-TOKEN (save it in your password manager. 
Can't be recovered!!):")
+       fmt.Println(accessTokenBase64)
+
+       return nil
+}
+
 func connectDatabase() error {
 
        u := read("Username: ")
@@ -234,6 +328,7 @@ func showHelp() error {
        fmt.Println("register wallee provider (", ACTION_REGISTER_PROVIDER, ")")
        fmt.Println("register wallee terminal (", ACTION_REGISTER_TERMINAL, ")")
        fmt.Println("deactivate wallee terminal (", ACTION_DEACTIVATE_TERMINAL, 
")")
+       fmt.Println("setup simulation (", ACTION_SETUP_SIMULATION, ")")
        fmt.Println("connect database (", ACTION_CONNECT_DB, ")")
        fmt.Println("show help (", ACTION_HELP, ")")
        fmt.Println("quit (", ACTION_QUIT, ")")
@@ -299,6 +394,8 @@ func dispatchCommand(cmd string) error {
                err = registerWalleeTerminal()
        case ACTION_DEACTIVATE_TERMINAL:
                err = deactivateTerminal()
+       case ACTION_SETUP_SIMULATION:
+               err = setupSimulation()
        default:
                fmt.Println("unknown action")
        }
diff --git a/cli/db.go b/cli/db.go
index 246e858..d75295a 100644
--- a/cli/db.go
+++ b/cli/db.go
@@ -7,7 +7,7 @@ const GET_PROVIDER_BY_NAME = "SELECT * FROM c2ec.provider WHERE 
name=$1"
 const GET_LAST_INSERTED_TERMINAL = "SELECT * FROM c2ec.terminal WHERE 
terminal_id = (SELECT MAX(terminal_id) FROM c2ec.terminal)"
 
 type Provider struct {
-       ProviderTerminalID int64  `db:"provider_id"`
+       ProviderId         int64  `db:"provider_id"`
        Name               string `db:"name"`
        PaytoTargetType    string `db:"payto_target_type"`
        BackendBaseURL     string `db:"backend_base_url"`
diff --git a/docs/content/.gitkeep 
b/docs/content/implementation/bank-integration.tex
similarity index 100%
copy from docs/content/.gitkeep
copy to docs/content/implementation/bank-integration.tex
diff --git a/docs/content/implementation/terminal-api.tex 
b/docs/content/implementation/terminal-api.tex
new file mode 100644
index 0000000..81ac4d5
--- /dev/null
+++ b/docs/content/implementation/terminal-api.tex
@@ -0,0 +1,27 @@
+\subsection{Terminal API}
+
+This section describes the Implementation of the Terminal API 
\cite{taler-terminal-api}.
+
+The C2EC component does not implement the \texttt{/quotas/*} endpoints, since 
those are not relevant for the withdrawal using a payment terminal.
+
+The exact specification can be found in (TODO REF APPENDIX)
+
+\subsubsection{Configuration (/config)}
+
+This endpoint returns the configuration. Especially the fields 
\texttt{currency} and \texttt{wire_type} are interesting, since they are used 
to 
+
+\subsubsection{Registering a withdrawal (/withdrawals)}
+
+The registration of a withdrawal initializes the flow by the 
+
+\subsubsection{Trigger Attestation (/withdrawals/[wopid]/check)}
+
+
+
+\subsubsection{Taler Integration (/taler-integration/*)}
+
+
+
+\subsubsection{Taler Integration (/taler-wire-gateway/*)}
+
+
diff --git a/docs/content/.gitkeep 
b/docs/content/implementation/wire-gateway.tex
similarity index 100%
copy from docs/content/.gitkeep
copy to docs/content/implementation/wire-gateway.tex
diff --git a/docs/project.bib b/docs/project.bib
index 32deea7..97400f7 100644
--- a/docs/project.bib
+++ b/docs/project.bib
@@ -226,6 +226,13 @@
     howpublished = 
{\url{https://docs.taler.net/core/api-corebank.html#authentication}}
 }
 
+@misc{taler-terminal-api,
+  author       = {Taler},
+  howpublished = {\url{https://docs.taler.net/core/api-terminal.html}},
+  title        = {Terminal API},
+  url          = 
{https://docs.taler.net/core/api-terminal.html#endpoints-for-integrated-sub-apis}
+}
+
 @misc{taler-design-document-49,
     author = {Taler},
     title = {Authentication},
diff --git a/simulation/c2ec-simulation b/simulation/c2ec-simulation
index d1066dd..781d796 100755
Binary files a/simulation/c2ec-simulation and b/simulation/c2ec-simulation 
differ
diff --git a/simulation/encoding.go b/simulation/encoding.go
index 98d1d4a..0ccaf53 100644
--- a/simulation/encoding.go
+++ b/simulation/encoding.go
@@ -1,51 +1,36 @@
 package main
 
 import (
-       "encoding/base32"
        "errors"
-       "fmt"
-       "net/url"
+       "math"
        "strings"
 )
 
-// 32 characters for decoding, using RFC 3548.
-const TALER_BASE32_CHARACTER_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
+func talerBinaryEncode(byts []byte) string {
 
-func talerBase32Encode(byts []byte) string {
-       return talerBase32Encoding().EncodeToString(byts)
+       return encodeCrock(byts)
+       //return talerBase32Encoding().EncodeToString(byts)
 }
 
-func talerBase32Decode(str string) ([]byte, error) {
+func talerBinaryDecode(str string) ([]byte, error) {
 
-       decoded, err := talerBase32Encoding().DecodeString(strings.ToUpper(str))
-       if err != nil {
-               return nil, err
-       }
-       return decoded, nil
-}
-
-func talerBase32Encoding() *base32.Encoding {
-       // 32 characters for decoding, using RFC 3548.
-       // character set copied from 
[TALER-EXCHANGE]/src/util/crypto_confirmation.c
-       return base32.NewEncoding(TALER_BASE32_CHARACTER_SET)
+       return decodeCrock(str)
+       // decoded, err := 
talerBase32Encoding().DecodeString(strings.ToUpper(str))
+       // if err != nil {
+       //      return nil, err
+       // }
+       // return decoded, nil
 }
 
 func ParseWopid(wopid string) ([]byte, error) {
 
-       unescaped, err := url.PathUnescape(wopid)
-       if err != nil {
-               fmt.Println("encoding", err)
-               return nil, errors.New("decoding failed")
-       }
-
-       wopidBytes, err := talerBase32Decode(unescaped)
+       wopidBytes, err := talerBinaryDecode(wopid)
        if err != nil {
                return nil, err
        }
 
        if len(wopidBytes) != 32 {
                err = errors.New("invalid wopid")
-               fmt.Println("encoding", err)
                return nil, err
        }
 
@@ -54,10 +39,105 @@ func ParseWopid(wopid string) ([]byte, error) {
 
 func FormatWopid(wopid []byte) string {
 
-       return url.PathEscape(talerBase32Encode(wopid))
+       return talerBinaryEncode(wopid)
 }
 
 func ParseEddsaPubKey(key EddsaPublicKey) ([]byte, error) {
 
-       return talerBase32Decode(string(key))
+       return talerBinaryDecode(string(key))
+}
+
+func FormatEddsaPubKey(key []byte) EddsaPublicKey {
+
+       return EddsaPublicKey(talerBinaryEncode(key))
+}
+
+func decodeCrock(e string) ([]byte, error) {
+       size := len(e)
+       bitpos := 0
+       bitbuf := 0
+       readPosition := 0
+       outLen := int(math.Floor((float64(size) * 5.0) / 8.0))
+       out := make([]byte, outLen)
+       outPos := 0
+
+       getValue := func(c byte) (int, error) {
+               alphabet := "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
+               switch c {
+               case 'o', 'O':
+                       return 0, nil
+               case 'i', 'I', 'l', 'L':
+                       return 1, nil
+               case 'u', 'U':
+                       return 27, nil
+               }
+
+               i := strings.IndexRune(alphabet, rune(c))
+               if i > -1 && i < 32 {
+                       return i, nil
+               }
+
+               return -1, errors.New("encoding error")
+       }
+
+       for readPosition < size || bitpos > 0 {
+               if readPosition < size {
+                       v, err := getValue(e[readPosition])
+                       if err != nil {
+                               return nil, err
+                       }
+                       readPosition++
+                       bitbuf = bitbuf<<5 | v
+                       bitpos += 5
+               }
+               for bitpos >= 8 {
+                       d := byte(bitbuf >> (bitpos - 8) & 0xff)
+                       out[outPos] = d
+                       outPos++
+                       bitpos -= 8
+               }
+               if readPosition == size && bitpos > 0 {
+                       bitbuf = bitbuf << (8 - bitpos) & 0xff
+                       if bitbuf == 0 {
+                               bitpos = 0
+                       } else {
+                               bitpos = 8
+                       }
+               }
+       }
+       return out, nil
+}
+
+func encodeCrock(data []byte) string {
+       out := ""
+       bitbuf := 0
+       bitpos := 0
+
+       encodeValue := func(value int) byte {
+               alphabet := "ABCDEFGHJKMNPQRSTVWXYZ"
+               switch {
+               case value >= 0 && value <= 9:
+                       return byte('0' + value)
+               case value >= 10 && value <= 31:
+                       return alphabet[value-10]
+               default:
+                       panic("Invalid value for encoding")
+               }
+       }
+
+       for _, b := range data {
+               bitbuf = bitbuf<<8 | int(b&0xff)
+               bitpos += 8
+               for bitpos >= 5 {
+                       value := bitbuf >> (bitpos - 5) & 0x1f
+                       out += string(encodeValue(value))
+                       bitpos -= 5
+               }
+       }
+       if bitpos > 0 {
+               bitbuf = bitbuf << (5 - bitpos)
+               value := bitbuf & 0x1f
+               out += string(encodeValue(value))
+       }
+       return out
 }
diff --git a/simulation/go.mod b/simulation/go.mod
index ff3212f..dde9d9d 100644
--- a/simulation/go.mod
+++ b/simulation/go.mod
@@ -1,3 +1,5 @@
 module c2ec-simulation
 
 go 1.22.1
+
+require github.com/gofrs/uuid v4.4.0+incompatible // indirect
diff --git a/simulation/go.sum b/simulation/go.sum
new file mode 100644
index 0000000..c0ad687
--- /dev/null
+++ b/simulation/go.sum
@@ -0,0 +1,2 @@
+github.com/gofrs/uuid v4.4.0+incompatible 
h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
+github.com/gofrs/uuid v4.4.0+incompatible/go.mod 
h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
diff --git a/simulation/http-util.go b/simulation/http-util.go
index afebb9f..72c7f67 100644
--- a/simulation/http-util.go
+++ b/simulation/http-util.go
@@ -3,7 +3,6 @@ package main
 
 import (
        "bytes"
-       "errors"
        "fmt"
        "net/http"
        "strings"
@@ -185,12 +184,6 @@ func HttpGet[T any](
        }
 
        if res.StatusCode > 299 {
-               errBody, err := NewJsonCodec[RFC9457Problem]().Decode(res.Body)
-               if err != nil {
-                       fmt.Println("error happened on GET. Failed parsing 
error")
-                       return nil, -1, err
-               }
-               fmt.Printf("Error (%d): %s (%s)", res.StatusCode, 
errBody.Title, errBody.Detail)
                return nil, res.StatusCode, nil
        }
 
@@ -202,101 +195,40 @@ func HttpGet[T any](
        }
 }
 
-// execute a POST request and parse response or retrieve error
-func HttpPost2[T any, R any](
-       req string,
-       body *T,
-       requestCodec Codec[T],
-       responseCodec Codec[R],
-) (*R, int, error) {
-
-       return HttpPost(
-               req,
-               nil,
-               nil,
-               body,
-               requestCodec,
-               responseCodec,
-       )
-}
-
-// execute a POST request and parse response or retrieve error
-// path- and query-parameters can be set to add query and path parameters
 func HttpPost[T any, R any](
-       req string,
-       pathParams map[string]string,
-       queryParams map[string]string,
+       url string,
+       headers map[string]string,
        body *T,
-       requestCodec Codec[T],
-       responseCodec Codec[R],
+       reqCodec Codec[T],
+       resCodec Codec[R],
 ) (*R, int, error) {
 
-       url := FormatUrl(req, pathParams, queryParams)
-       fmt.Println("POST:", url)
-
-       var res *http.Response
-       if body == nil {
-               if requestCodec == nil {
-                       res, err := http.Post(
-                               url,
-                               "",
-                               nil,
-                       )
-
-                       if err != nil {
-                               return nil, -1, err
-                       }
-
-                       return nil, res.StatusCode, nil
-               } else {
-                       return nil, -1, errors.New("invalid arguments - body 
was not present but codec was defined")
-               }
-       } else {
-               if requestCodec == nil {
-                       return nil, -1, errors.New("invalid arguments - body 
was present but no codec was defined")
-               } else {
-
-                       encodedBody, err := requestCodec.Encode(body)
-                       if err != nil {
-                               return nil, -1, err
-                       }
-
-                       res, err = http.Post(
-                               url,
-                               requestCodec.HttpApplicationContentHeader(),
-                               encodedBody,
-                       )
-
-                       if err != nil {
-                               return nil, -1, err
-                       }
-
-                       buf := make([]byte, res.ContentLength)
-                       _, err = res.Body.Read(buf)
-                       if err != nil {
-                               fmt.Println("body after post:", string(buf))
-                       }
-               }
+       bodyEncoded, err := reqCodec.EncodeToBytes(body)
+       if err != nil {
+               return nil, -1, err
        }
 
-       if responseCodec == nil {
-               return nil, res.StatusCode, nil
+       req, err := http.NewRequest(HTTP_POST, url, 
bytes.NewBuffer(bodyEncoded))
+       if err != nil {
+               return nil, -1, err
        }
 
-       if res.StatusCode > 299 {
-               errBody, err := NewJsonCodec[RFC9457Problem]().Decode(res.Body)
-               if err != nil {
-                       return nil, -1, err
-               }
-               fmt.Printf("Error (%d): %s (%s)", res.StatusCode, 
errBody.Title, errBody.Detail)
-               return nil, res.StatusCode, nil
+       for k, v := range headers {
+               req.Header.Add(k, v)
        }
+       req.Header.Add("Accept", reqCodec.HttpApplicationContentHeader())
 
-       resBody, err := responseCodec.Decode(res.Body)
+       res, err := http.DefaultClient.Do(req)
        if err != nil {
                return nil, -1, err
        }
-       return resBody, res.StatusCode, err
+
+       if resCodec == nil {
+               return nil, res.StatusCode, err
+       } else {
+               resBody, err := resCodec.Decode(res.Body)
+               return resBody, res.StatusCode, err
+       }
 }
 
 // builds request URL containing the path and query
diff --git a/simulation/main.go b/simulation/main.go
index 3eef8cd..7e9bbd4 100644
--- a/simulation/main.go
+++ b/simulation/main.go
@@ -8,11 +8,6 @@ import (
 const DISABLE_DELAYS = true
 
 const C2EC_BASE_URL = "http://localhost:8082";
-const C2EC_BANK_BASE_URL = C2EC_BASE_URL + "/c2ec"
-const C2EC_BANK_CONFIG_URL = C2EC_BANK_BASE_URL + "/config"
-const C2EC_BANK_WITHDRAWAL_STATUS_URL = C2EC_BANK_BASE_URL + 
"/withdrawal-operation/:wopid"
-const C2EC_BANK_WITHDRAWAL_REGISTRATION_URL = C2EC_BANK_BASE_URL + 
"/withdrawal-operation/:wopid"
-const C2EC_BANK_WITHDRAWAL_PAYMENT_URL = C2EC_BANK_BASE_URL + 
"/withdrawal-operation/:wopid/confirm"
 
 // simulates the terminal talking to its backend system and executing the 
payment.
 const PROVIDER_BACKEND_PAYMENT_DELAY_MS = 1000
@@ -26,25 +21,6 @@ const TERMINAL_ACCEPT_CARD_DELAY_MS = 5000
 // simulates the user scanning the QR code presented at the terminal
 const WALLET_SCAN_QR_CODE_DELAY_MS = 5000
 
-// 
https://docs.taler.net/core/api-exchange.html#tsref-type-CurrencySpecification
-type CurrencySpecification struct {
-       Name                            string `json:"name"`
-       Currency                        string `json:"currency"`
-       NumFractionalInputDigits        int    
`json:"num_fractional_input_digits"`
-       NumFractionalNormalDigits       int    
`json:"num_fractional_normal_digits"`
-       NumFractionalTrailingZeroDigits int    
`json:"num_fractional_trailing_zero_digits"`
-       AltUnitNames                    string `json:"alt_unit_names"`
-}
-
-// 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankIntegrationConfig
-type BankIntegrationConfig struct {
-       Name                  string                `json:"name"`
-       Version               string                `json:"version"`
-       Implementation        string                `json:"implementation"`
-       Currency              string                `json:"currency"`
-       CurrencySpecification CurrencySpecification 
`json:"currency_specification"`
-}
-
 type SimulatedPhysicalInteraction struct {
        Msg string
 }
diff --git a/simulation/model.go b/simulation/model.go
index 51c94b4..ba55911 100644
--- a/simulation/model.go
+++ b/simulation/model.go
@@ -60,10 +60,3 @@ type C2ECWithdrawalStatus struct {
        WireTypes     []string                  `json:"wire_types"`
        ReservePubKey EddsaPublicKey            `json:"selected_reserve_pub"`
 }
-
-type C2ECPaymentNotification struct {
-       ProviderTransactionId string `json:"provider_transaction_id"`
-       TerminalId            int    `json:"terminal_id"`
-       Amount                Amount `json:"amount"`
-       Fees                  Amount `json:"card_fees"`
-}
diff --git a/simulation/sim-terminal.go b/simulation/sim-terminal.go
index a639fc8..827614d 100644
--- a/simulation/sim-terminal.go
+++ b/simulation/sim-terminal.go
@@ -1,65 +1,131 @@
 package main
 
 import (
-       "bytes"
-       "crypto/rand"
        "encoding/base64"
        "errors"
        "fmt"
-       "net/http"
        "strconv"
        "time"
+
+       "github.com/gofrs/uuid"
 )
 
+const C2EC_TERMINAL_CONFIG_API = C2EC_BASE_URL + "/config"
+const C2EC_TERMINAL_SETUP_WITHDRAWAL_API = C2EC_BASE_URL + "/withdrawals"
+const C2EC_TERMINAL_STATUS_WITHDRAWAL_API = C2EC_BASE_URL + 
"/withdrawals/:wopid"
+const C2EC_TERMINAL_CHECK_WITHDRAWAL_API = C2EC_BASE_URL + 
"/withdrawals/:wopid/check"
+
 const TERMINAL_PROVIDER = "Simulation"
 
-const TERMINAL_ID = "1"
+// this must be the id retrieved by the cli
+const TERMINAL_ID = "2"
 
 // retrieved from the cli tool when added the terminal
-const TERMINAL_USER_ID = TERMINAL_PROVIDER + "-" + TERMINAL_ID
+const TERMINAL_USER_ID = "Simulation-" + TERMINAL_ID
 
 // retrieved from the cli tool when added the terminal
-const TERMINAL_ACCESS_TOKEN = "secret"
+const TERMINAL_ACCESS_TOKEN = "oVclsDlWVl0LaQg83e05M7/vCk2PfdJ785GaI0MQ0wc="
 
 const SIM_TERMINAL_LONG_POLL_MS_STR = "20000" // 20 seconds
 
-const QR_CODE_CONTENT_BASE = "taler://withdraw/localhost:8082/c2ec/"
+const QR_CODE_CONTENT_BASE = 
"taler://withdraw/localhost:8082/taler-integration/"
 
 func Terminal(in chan *SimulatedPhysicalInteraction, out chan 
*SimulatedPhysicalInteraction, kill chan error) {
 
        fmt.Println("TERMINAL: Terminal idle... awaiting readiness message of 
sim-wallet")
        <-in
 
-       fmt.Println("TERMINAL: Sim-Wallet ready, generating WOPID...    ")
-       wopidBytes := make([]byte, 32)
-       _, err := rand.Read(wopidBytes)
+       fmt.Println("TERMINAL: basic auth header:", TerminalAuth())
+       fmt.Println("TERMINAL: loading terminal api config")
+       terminalApiCfg, status, err := HttpGet(
+               C2EC_TERMINAL_CONFIG_API,
+               map[string]string{"Authorization": TerminalAuth()},
+               NewJsonCodec[TerminalConfig](),
+       )
+       if err != nil {
+               kill <- err
+               return
+       }
+       if status != 200 {
+               kill <- errors.New("terminal api configuration failed with 
status " + strconv.Itoa(status))
+               return
+       }
+       fmt.Println("TERMINAL: API config loaded.", terminalApiCfg.Name, 
terminalApiCfg.Version, terminalApiCfg.ProviderName, terminalApiCfg.WireType)
+
+       fmt.Println("TERMINAL: Sim-Wallet ready, intiating withdrawal...")
+
+       uuid, err := uuid.NewGen().NewV7()
+       if err != nil {
+               kill <- err
+               return
+       }
+
+       setupReq := &TerminalWithdrawalSetup{
+               Amount:                &Amount{"CHF", 10, 50},
+               SuggestedAmount:       &Amount{"CHF", 10, 50},
+               ProviderTransactionId: "",
+               TerminalFees:          &Amount{},
+               RequestUid:            uuid.String(),
+               UserUuid:              "",
+               Lock:                  "",
+       }
+
+       url := FormatUrl(
+               C2EC_TERMINAL_SETUP_WITHDRAWAL_API,
+               map[string]string{},
+               map[string]string{},
+       )
+       fmt.Println("TERMINAL: requesting url:", url)
+       response, status, err := HttpPost(
+               url,
+               map[string]string{"Authorization": TerminalAuth()},
+               setupReq,
+               NewJsonCodec[TerminalWithdrawalSetup](),
+               NewJsonCodec[TerminalWithdrawalSetupResponse](),
+       )
+       if err != nil {
+               kill <- err
+               return
+       }
+       if status != 200 {
+               kill <- errors.New("status of withdrawal setup response was " + 
strconv.Itoa(status))
+               return
+       }
+
+       wopidEncoded := response.Wopid
+       fmt.Println("TERMINAL: received wopid:", wopidEncoded)
+
+       // this decoding encoding cycle is useless but tests
+       // decoding and encoding of the wopid. That's why it is
+       // done here.
+       wopidDecoded, err := ParseWopid(wopidEncoded)
        if err != nil {
-               fmt.Println("TERMINAL: failed creating the wopid:", 
err.Error(), "(ends simulation)")
                kill <- err
+               return
        }
+       wopidEncoded = FormatWopid(wopidDecoded)
 
-       wopid := FormatWopid(wopidBytes)
-       fmt.Println("TERMINAL: Generated Nonce (base64 url encoded):", wopid)
-       uri := QR_CODE_CONTENT_BASE + wopid
+       uri := QR_CODE_CONTENT_BASE + wopidEncoded
        fmt.Println("TERMINAL: Taler Withdrawal URI:", uri)
 
        // note for realworld implementation
        // -> start long polling always before showing the QR code
-       awaitSelection := make(chan *C2ECWithdrawalStatus)
+       awaitSelection := make(chan *BankWithdrawalOperationStatus)
        longPollFailed := make(chan error)
 
        fmt.Println("TERMINAL: now sending long poll request to c2ec from 
terminal and await parameter selection")
        go func() {
 
                url := FormatUrl(
-                       C2EC_BANK_WITHDRAWAL_STATUS_URL,
-                       map[string]string{"wopid": wopid},
+                       C2EC_TERMINAL_STATUS_WITHDRAWAL_API,
+                       map[string]string{"wopid": wopidEncoded},
                        map[string]string{"long_poll_ms": 
SIM_TERMINAL_LONG_POLL_MS_STR},
                )
+               fmt.Println("TERMINAL: requesting status update for 
withdrawal", url)
                response, status, err := HttpGet(
                        url,
                        map[string]string{"Authorization": TerminalAuth()},
-                       NewJsonCodec[C2ECWithdrawalStatus](),
+                       NewJsonCodec[BankWithdrawalOperationStatus](),
                )
                if err != nil {
                        kill <- err
@@ -73,7 +139,7 @@ func Terminal(in chan *SimulatedPhysicalInteraction, out 
chan *SimulatedPhysical
                awaitSelection <- response
        }()
 
-       fmt.Println("Go is too fast :) ... need to sleep a bit that long 
polling request is guaranteed to be executed before the POST of the 
registration. This won't be a problem in real world appliance.")
+       fmt.Println("need to sleep a bit that long polling request is 
guaranteed to be executed before the POST of the registration. This won't be a 
problem in real world appliance.")
        time.Sleep(time.Duration(10) * time.Millisecond)
 
        if !DISABLE_DELAYS {
@@ -100,50 +166,39 @@ func Terminal(in chan *SimulatedPhysicalInteraction, out 
chan *SimulatedPhysical
                                fmt.Println("TERMINAL: card accepted. terminal 
waits for response of provider backend.")
                        }
 
-                       terminalId, err := strconv.Atoi(TERMINAL_ID)
-                       if err != nil {
-                               fmt.Println("failed parsing the terminal id.")
-                               kill <- err
-                       }
-
-                       fmt.Println("TERMINAL: payment was processed at the 
provider backend. sending payment notification.")
-                       paymentNotification := &C2ECPaymentNotification{
+                       fmt.Println("TERMINAL: payment was processed at the 
provider backend. sending check notification.")
+                       checkNotification := 
&TerminalWithdrawalConfirmationRequest{
                                ProviderTransactionId: 
"simulation-transaction-id-0",
-                               TerminalId:            terminalId,
-                               Amount: Amount{
-                                       Currency: "CHF",
-                                       Fraction: 10,
-                                       Value:    10,
-                               },
-                               Fees: Amount{
+                               TerminalFees: &Amount{
                                        Currency: "CHF",
                                        Fraction: 10,
                                        Value:    0,
                                },
                        }
-                       cdc := NewJsonCodec[C2ECPaymentNotification]()
-                       pnbytes, err := cdc.EncodeToBytes(paymentNotification)
-                       if err != nil {
-                               fmt.Println("TERMINAL: failed serializing 
payment notification")
-                               kill <- err
-                       }
-                       paymentUrl := FormatUrl(
-                               C2EC_BANK_WITHDRAWAL_PAYMENT_URL,
-                               map[string]string{"wopid": wopid},
+                       checkurl := FormatUrl(
+                               C2EC_TERMINAL_CHECK_WITHDRAWAL_API,
+                               map[string]string{"wopid": wopidEncoded},
                                map[string]string{},
                        )
-                       _, err = http.Post(
-                               paymentUrl,
-                               cdc.HttpApplicationContentHeader(),
-                               bytes.NewReader(pnbytes),
+                       fmt.Println("TERMINAL: check url", checkurl)
+                       _, status, err = 
HttpPost[TerminalWithdrawalConfirmationRequest, any](
+                               checkurl,
+                               map[string]string{"Authorization": 
TerminalAuth()},
+                               checkNotification,
+                               
NewJsonCodec[TerminalWithdrawalConfirmationRequest](),
+                               nil,
                        )
                        if err != nil {
                                fmt.Println("TERMINAL: error on POST request:", 
err.Error())
                                kill <- err
                        }
+                       if status != 204 {
+                               fmt.Println("TERMINAL: error while check 
payment POST: " + strconv.Itoa(status))
+                               kill <- errors.New("payment check request by 
terminal failed")
+                       }
                        fmt.Println("TERMINAL: Terminal flow ended")
                case f := <-longPollFailed:
-                       fmt.Println("TERMINAL: long-polling for selection 
failed... error:", err.Error())
+                       fmt.Println("TERMINAL: long-polling for selection 
failed... error:", err)
                        kill <- f
                }
        }
@@ -152,5 +207,34 @@ func Terminal(in chan *SimulatedPhysicalInteraction, out 
chan *SimulatedPhysical
 func TerminalAuth() string {
 
        userAndPw := fmt.Sprintf("%s:%s", TERMINAL_USER_ID, 
TERMINAL_ACCESS_TOKEN)
-       return base64.StdEncoding.EncodeToString([]byte(userAndPw))
+       return "Basic " + base64.StdEncoding.EncodeToString([]byte(userAndPw))
+}
+
+// Structs copied from c2ec
+type TerminalConfig struct {
+       Name         string `json:"name"`
+       Version      string `json:"version"`
+       ProviderName string `json:"provider_name"`
+       WireType     string `json:"wire_type"`
+}
+
+type TerminalWithdrawalSetup struct {
+       Amount                *Amount `json:"amount"`
+       SuggestedAmount       *Amount `json:"suggested_amount"`
+       ProviderTransactionId string  `json:"provider_transaction_id"`
+       TerminalFees          *Amount `json:"terminal_fees"`
+       RequestUid            string  `json:"request_uid"`
+       UserUuid              string  `json:"user_uuid"`
+       Lock                  string  `json:"lock"`
+}
+
+type TerminalWithdrawalSetupResponse struct {
+       Wopid string `json:"withdrawal_id"`
+}
+
+type TerminalWithdrawalConfirmationRequest struct {
+       ProviderTransactionId string  `json:"provider_transaction_id"`
+       TerminalFees          *Amount `json:"terminal_fees"`
+       UserUuid              string  `json:"user_uuid"`
+       Lock                  string  `json:"lock"`
 }
diff --git a/simulation/sim-wallet.go b/simulation/sim-wallet.go
index 46dd5f7..0a6f17e 100644
--- a/simulation/sim-wallet.go
+++ b/simulation/sim-wallet.go
@@ -12,6 +12,11 @@ import (
        "time"
 )
 
+const C2EC_BANK_BASE_URL = C2EC_BASE_URL + "/taler-integration"
+const C2EC_BANK_CONFIG_URL = C2EC_BANK_BASE_URL + "/config"
+const C2EC_BANK_WITHDRAWAL_STATUS_URL = C2EC_BANK_BASE_URL + 
"/withdrawal-operation/:wopid"
+const C2EC_BANK_WITHDRAWAL_REGISTRATION_URL = C2EC_BANK_BASE_URL + 
"/withdrawal-operation/:wopid"
+
 const SIM_WALLET_LONG_POLL_MS_STR = "10000" // 10 seconds
 
 func Wallet(in chan *SimulatedPhysicalInteraction, out chan 
*SimulatedPhysicalInteraction, kill chan error) {
@@ -37,9 +42,11 @@ func Wallet(in chan *SimulatedPhysicalInteraction, out chan 
*SimulatedPhysicalIn
                map[string]string{},
        )
 
-       cdc := NewJsonCodec[C2ECWithdrawRegistration]()
-       reg := new(C2ECWithdrawRegistration)
+       cdc := NewJsonCodec[BankWithdrawalOperationPostRequest]()
+       reg := new(BankWithdrawalOperationPostRequest)
        reg.ReservePubKey = EddsaPublicKey(simulateReservePublicKey())
+       reg.Amount = nil
+       reg.SelectedExchange = C2EC_BANK_BASE_URL
        body, err := cdc.EncodeToBytes(reg)
        regByte := bytes.NewBuffer(body)
        // fmt.Println("WALLET  : body (bytes):", regByte.Bytes())
@@ -128,5 +135,44 @@ func simulateReservePublicKey() string {
        if err != nil {
                return ""
        }
-       return talerBase32Encode(mockedPubKey)
+       return talerBinaryEncode(mockedPubKey)
+}
+
+type CurrencySpecification struct {
+       Name                            string `json:"name"`
+       Currency                        string `json:"currency"`
+       NumFractionalInputDigits        int    
`json:"num_fractional_input_digits"`
+       NumFractionalNormalDigits       int    
`json:"num_fractional_normal_digits"`
+       NumFractionalTrailingZeroDigits int    
`json:"num_fractional_trailing_zero_digits"`
+       AltUnitNames                    string `json:"alt_unit_names"`
+}
+
+// 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankIntegrationConfig
+type BankIntegrationConfig struct {
+       Name                  string                `json:"name"`
+       Version               string                `json:"version"`
+       Implementation        string                `json:"implementation"`
+       Currency              string                `json:"currency"`
+       CurrencySpecification CurrencySpecification 
`json:"currency_specification"`
+       // TODO: maybe add exchanges payto uri for transfers etc.?
+}
+
+type BankWithdrawalOperationPostRequest struct {
+       ReservePubKey    EddsaPublicKey `json:"reserve_pub"`
+       SelectedExchange string         `json:"selected_exchange"`
+       Amount           *Amount        `json:"amount"`
+}
+
+type BankWithdrawalOperationPostResponse struct {
+       Status             WithdrawalOperationStatus `json:"status"`
+       ConfirmTransferUrl string                    
`json:"confirm_transfer_url"`
+       TransferDone       bool                      `json:"transfer_done"`
+}
+
+type BankWithdrawalOperationStatus struct {
+       Status        WithdrawalOperationStatus `json:"status"`
+       Amount        Amount                    `json:"amount"`
+       SenderWire    string                    `json:"sender_wire"`
+       WireTypes     []string                  `json:"wire_types"`
+       ReservePubKey EddsaPublicKey            `json:"selected_reserve_pub"`
 }
diff --git a/wallee-c2ec/app/src/main/AndroidManifest.xml 
b/wallee-c2ec/app/src/main/AndroidManifest.xml
index f276936..5d5d90f 100644
--- a/wallee-c2ec/app/src/main/AndroidManifest.xml
+++ b/wallee-c2ec/app/src/main/AndroidManifest.xml
@@ -2,6 +2,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android";
     xmlns:tools="http://schemas.android.com/tools";>
 
+    <uses-permission android:name="android.permission.INTERNET" />
+
     <application
         android:allowBackup="true"
         android:dataExtractionRules="@xml/data_extraction_rules"
diff --git 
a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CryptoUtils.kt
 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CryptoUtils.kt
new file mode 100644
index 0000000..ab1880a
--- /dev/null
+++ 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CryptoUtils.kt
@@ -0,0 +1,105 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2022 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/*
+ * The code in this file was copied from the Taler Wallet App
+ * source:  
https://git.taler.net/taler-android.git/taler-kotlin-android/src/main/java/net/taler/common/CyptoUtils.kt
+ */
+
+package ch.bfh.habej2.wallee_c2ec.client.taler.encoding
+
+import kotlin.math.floor
+
+object CyptoUtils {
+    internal fun getValue(c: Char): Int {
+        val a = when (c) {
+            'o','O' -> '0'
+            'i','I','l','L' -> '1'
+            'u','U' -> 'V'
+            else -> c
+        }
+        if (a in '0'..'9') {
+            return a - '0'
+        }
+        val A = if (a in 'a'..'z') a.uppercaseChar() else a
+        var dec = 0
+        if (A in 'A'..'Z') {
+            if ('I' < A) dec++
+            if ('L' < A) dec++
+            if ('O' < A) dec++
+            if ('U' < A) dec++
+            return A - 'A' + 10 - dec
+        }
+        throw Error("encoding error")
+    }
+
+    fun decodeCrock(e: String): ByteArray {
+        val size = e.length
+        var bitpos = 0
+        var bitbuf = 0
+        var readPosition = 0
+        val outLen = floor((size * 5f) / 8).toInt()
+        val out = ByteArray(outLen)
+        var outPos = 0
+        while (readPosition < size || bitpos > 0) {
+            if (readPosition < size) {
+                val v = getValue(e[readPosition++])
+                bitbuf = bitbuf.shl(5).or(v)
+                bitpos += 5
+            }
+            while (bitpos >= 8) {
+                val d = bitbuf.shr(bitpos -8).and(0xff).toByte()
+                out[outPos++] = d
+                bitpos -= 8
+            }
+            if (readPosition == size && bitpos > 0) {
+                bitbuf = bitbuf.shl( 8 - bitpos).and(0xff)
+                bitpos = if (bitbuf == 0) 0 else 8
+            }
+        }
+        return out
+    }
+
+    fun encodeCrock(data: ByteArray): String {
+        val out = StringBuilder()
+        var bitbuf = 0
+        var bitpos = 0
+        for (byte in data) {
+            bitbuf = bitbuf.shl(8).or(byte.toInt() and 0xff)
+            bitpos += 8
+            while (bitpos >= 5) {
+                val value = bitbuf.shr(bitpos - 5).and(0x1f)
+                out.append(encodeValue(value))
+                bitpos -= 5
+            }
+        }
+        if (bitpos > 0) {
+            bitbuf = bitbuf.shl(5 - bitpos)
+            val value = bitbuf.and(0x1f)
+            out.append(encodeValue(value))
+        }
+        return out.toString()
+    }
+
+    private fun encodeValue(value: Int): Char {
+        val alphabet = "ABCDEFGHJKMNPQRSTVWXYZ"
+        return when (value) {
+            in 0..9 -> ('0'.code + value).toChar() // '0' to '9'
+            in 10..31 -> alphabet[value - 10] // 'A' to 'Z' (without 'i' 'l' 
'o' 'u')
+            else -> throw IllegalArgumentException("Invalid value for 
encoding: $value")
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/TalerBase32Codec.kt
 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/TalerBase32Codec.kt
index 5464858..d5ebea8 100644
--- 
a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/TalerBase32Codec.kt
+++ 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/TalerBase32Codec.kt
@@ -1,8 +1,16 @@
 package ch.bfh.habej2.wallee_c2ec.client.taler.encoding
 
-import android.util.Base64
-import org.apache.commons.codec.binary.Base32
+import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtils.decodeCrock
+import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtils.encodeCrock
+
+fun Base32Encode(byts: ByteArray): String = encodeCrock(byts)
+
+fun Base32Decode(enc: String) = decodeCrock(enc)
+
+
+
+
+
+
 
-fun Base32Encode(byts: ByteArray): String = Base64.encodeToString(byts, 0) // 
Base32().encodeAsString(byts)
 
-fun Base32Decode(enc: String) = Base32().decode(enc)
\ No newline at end of file
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
index 5419ce9..be0f601 100644
--- 
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,9 +1,10 @@
 package ch.bfh.habej2.wallee_c2ec.client.wallee
 
+import ch.bfh.habej2.wallee_c2ec.withdrawal.WithdrawalActivity
 import com.wallee.android.till.sdk.ResponseHandler
 import com.wallee.android.till.sdk.data.TransactionResponse
 
-class WalleeResponseHandler : ResponseHandler() {
+class WalleeResponseHandler(private val activity: WithdrawalActivity) : 
ResponseHandler() {
 
     override fun authorizeTransactionReply(response: TransactionResponse?) {
 
@@ -16,4 +17,15 @@ class WalleeResponseHandler : ResponseHandler() {
 
         response.transaction.metaData.get("id")
     }
+
+    override fun checkApiServiceCompatibilityReply(
+        isCompatible: Boolean?,
+        apiServiceVersion: String?
+    ) {
+
+        if (isCompatible == null || !isCompatible) {
+            // just dont start withdrawals when api is not compatible
+            activity.finish()
+        }
+    }
 }
\ 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
new file mode 100644
index 0000000..e22ea0a
--- /dev/null
+++ 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AmountScreen.kt
@@ -0,0 +1,51 @@
+package ch.bfh.habej2.wallee_c2ec.withdrawal
+
+import android.app.Activity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.input.KeyboardType
+
+@Composable
+fun AmountScreen(model: WithdrawalViewModel, navigateToWhenAmountEntered: () 
-> Unit) {
+
+    val activity = LocalContext.current as Activity
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+
+        Text(text = "present card, trigger payment")
+
+        TextField(
+            value = "",
+            onValueChange = {
+                model.updateAmount(it)
+            },
+            label = { Text(text = "Enter amount") },
+            placeholder = { Text(text = "amount") },
+            keyboardOptions = KeyboardOptions(
+                autoCorrect = false,
+                keyboardType = KeyboardType.Number
+            )
+        )
+
+        Button(onClick = {
+            navigateToWhenAmountEntered()
+        }) {
+            Text(text = "pay")
+        }
+
+        Button(onClick = {
+            model.withdrawalOperationFailed()
+            activity.finish()
+        }) {
+            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
new file mode 100644
index 0000000..a82213a
--- /dev/null
+++ 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/AuthorizePaymentScreen.kt
@@ -0,0 +1,57 @@
+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 androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalContext
+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
+
+@Composable
+fun AuthorizePaymentScreen(model: WithdrawalViewModel, client: ApiClient) {
+
+    val uiState by model.uiState.collectAsState()
+    val activity = LocalContext.current as Activity
+
+    val withdrawalAmount = LineItem
+        .ListBuilder(
+            uiState.encodedWopid,
+            BigDecimal("${uiState.amount.value}.${uiState.amount.frac}")
+        )
+        .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)
+    } catch (e: Exception) {
+        model.withdrawalOperationFailed()
+        activity.finish()
+        e.printStackTrace()
+    }
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+
+        Text(text = "Transaction Executed")
+
+        Button(onClick = { activity.finish() }) {
+            Text(text = "finish")
+        }
+    }
+}
diff --git 
a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ExchangeSelectionScreen.kt
 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ExchangeSelectionScreen.kt
new file mode 100644
index 0000000..458b158
--- /dev/null
+++ 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/ExchangeSelectionScreen.kt
@@ -0,0 +1,42 @@
+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 androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalContext
+import ch.bfh.habej2.wallee_c2ec.client.taler.config.TalerBankIntegrationConfig
+
+@Composable
+fun ExchangeSelectionScreen(
+    model: WithdrawalViewModel,
+    onNavigateToWithdrawal: () -> Unit
+) {
+
+    val activity = LocalContext.current as Activity
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+
+        Text(text = "Choose the exchange to withdraw from")
+
+        // TODO let user select exchanges from config here
+        //  config must contain display name, credentials (generated by cli)
+        //  and the base url of the c2ec bank-integration api
+
+        val ctx = LocalContext.current
+        Button(onClick = {
+            model.exchangeUpdated(TalerBankIntegrationConfig("","","",""))
+            onNavigateToWithdrawal()
+        }) {
+            Text(text = "withdraw")
+        }
+
+        Button(onClick = { activity.finish() }) {
+            Text(text = "abort")
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterWithdrawalScreen.kt
 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterWithdrawalScreen.kt
new file mode 100644
index 0000000..59a43fd
--- /dev/null
+++ 
b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/withdrawal/RegisterWithdrawalScreen.kt
@@ -0,0 +1,44 @@
+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 androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun RegisterWithdrawalScreen(
+    model: WithdrawalViewModel,
+    navigateToWhenRegistered: () -> Unit
+) {
+
+    val uiState by model.uiState.collectAsState()
+    val activity = (LocalContext.current as Activity)
+
+    model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) {
+        activity.finish()
+    }
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+
+        Text(text = "QR-Code content: 
${formatTalerUri(uiState.exchangeBankIntegrationApiUrl, uiState.encodedWopid)}")
+
+        QRCode(formatTalerUri(uiState.exchangeBankIntegrationApiUrl, 
uiState.encodedWopid))
+
+        Button(onClick = {
+            model.withdrawalOperationFailed()
+            activity.finish()
+        }) {
+            Text(text = "abort")
+        }
+    }
+}
+
+private fun formatTalerUri(exchangeBankIntegrationApiPath: String, 
encodedWopid: String) =
+    "taler://withdraw/$exchangeBankIntegrationApiPath/$encodedWopid"
\ 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
index 526924c..c4c4d2c 100644
--- 
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
@@ -29,9 +29,15 @@ import java.util.Currency
 
 class WithdrawalActivity : ComponentActivity() {
 
+    private lateinit var walleeClient: ApiClient
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        walleeClient = ApiClient(WalleeResponseHandler(this))
+        walleeClient.bind(this)
+        walleeClient.checkApiServiceCompatibility()
+
         setContent {
 
             val model = WithdrawalViewModel()
@@ -54,155 +60,34 @@ class WithdrawalActivity : ComponentActivity() {
                     }
                 }
                 composable("authorizePaymentScreen") {
-                    AuthorizePaymentScreen(model)
+                    AuthorizePaymentScreen(model, walleeClient)
                 }
             }
         }
     }
-}
-
-@Composable
-fun RegisterWithdrawalScreen(
-    model: WithdrawalViewModel,
-    navigateToWhenRegistered: () -> Unit
-) {
-
-    val uiState by model.uiState.collectAsState()
-    val activity = (LocalContext.current as Activity)
-
-    model.startAuthorizationWhenReadyOrAbort(navigateToWhenRegistered) {
-        activity.finish()
-    }
-
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally
-    ) {
-
-        Text(text = "QR-Code content: ${formatTalerUri(uiState.encodedWopid)}")
-
-        QRCode(formatTalerUri(uiState.encodedWopid))
 
-        Button(onClick = {
-            model.withdrawalOperationFailed()
-            activity.finish()
-        }) {
-            Text(text = "abort")
-        }
+    override fun onStart() {
+        super.onStart()
+        walleeClient.bind(this)
     }
-}
-
-@Composable
-fun AmountScreen(model: WithdrawalViewModel, navigateToWhenAmountEntered: () 
-> Unit) {
-
-    val activity = LocalContext.current as Activity
-
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally
-    ) {
 
-        Text(text = "present card, trigger payment")
-
-        TextField(
-            value = "",
-            onValueChange = {
-                model.updateAmount(it)
-            },
-            label = { Text(text = "Enter amount") },
-            placeholder = { Text(text = "amount") },
-            keyboardOptions = KeyboardOptions(
-                autoCorrect = false,
-                keyboardType = KeyboardType.Number
-            )
-        )
-
-        Button(onClick = {
-            navigateToWhenAmountEntered()
-        }) {
-            Text(text = "pay")
-        }
-
-        Button(onClick = {
-            model.withdrawalOperationFailed()
-            activity.finish()
-        }) {
-            Text(text = "abort")
-        }
+    override fun onResume() {
+        super.onResume()
+        walleeClient.bind(this)
     }
-}
-
-@Composable
-fun ExchangeSelectionScreen(
-    model: WithdrawalViewModel,
-    onNavigateToWithdrawal: () -> Unit
-) {
-
-    val activity = LocalContext.current as Activity
-
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally
-    ) {
-
-        Text(text = "Choose the exchange to withdraw from")
 
-        // TODO let user select exchanges from config here
-        //  config must contain display name, credentials (generated by cli)
-        //  and the base url of the c2ec bank-integration api
-
-        val ctx = LocalContext.current
-        Button(onClick = {
-            model.exchangeUpdated(TalerBankIntegrationConfig("","","",""))
-            onNavigateToWithdrawal()
-        }) {
-            Text(text = "withdraw")
-        }
-
-        Button(onClick = { activity.finish() }) {
-            Text(text = "abort")
-        }
+    override fun onStop() {
+        super.onStop()
+        walleeClient.unbind(this)
     }
-}
 
-@Composable
-fun AuthorizePaymentScreen(model: WithdrawalViewModel) {
-
-    val uiState by model.uiState.collectAsState()
-    val activity = LocalContext.current as Activity
-    val client = ApiClient(WalleeResponseHandler())
-
-    client.bind(activity)
-
-    val withdrawalAmount = LineItem
-        .ListBuilder(
-            uiState.encodedWopid,
-            BigDecimal("${uiState.amount.value}.${uiState.amount.frac}")
-        )
-        .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)
-    } catch (e: Exception) {
-        e.printStackTrace()
+    override fun onDestroy() {
+        super.onDestroy()
+        walleeClient.unbind(this)
     }
 
-    client.unbind(activity)
-
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally
-    ) {
-
-        Text(text = "Transaction Executed")
-
-        Button(onClick = { activity.finish() }) {
-            Text(text = "finish")
-        }
+    override fun onPause() {
+        super.onPause()
+        walleeClient.unbind(this)
     }
 }
-
-private fun formatTalerUri(encodedWopid: String) = 
"taler://withdraw/$encodedWopid"
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
new file mode 100644
index 0000000..e207843
--- /dev/null
+++ 
b/wallee-c2ec/app/src/test/java/ch/bfh/habej2/wallee_c2ec/client/taler/encoding/CyptoUtilsTest.kt
@@ -0,0 +1,54 @@
+package ch.bfh.habej2.wallee_c2ec.client.taler.encoding
+
+import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtils.decodeCrock
+import ch.bfh.habej2.wallee_c2ec.client.taler.encoding.CyptoUtils.encodeCrock
+import org.junit.Test
+import java.security.SecureRandom
+
+class CyptoUtilsTest {
+
+    @Test
+    fun crockford() {
+
+        val origin = rand32Bytes()
+        println("origin: $origin")
+        val encoded = encodeCrock(origin)
+        println("encoded: $encoded")
+        val decoded = decodeCrock(encoded)
+        println("decoded: $decoded")
+
+        assert(origin.contentEquals(decoded))
+    }
+
+    @Test
+    fun crockford_taler_special() {
+        // see https://docs.taler.net/core/api-common.html#binary-data
+
+        val origin = "BNR1DRKYZ676T5KMHJMNPV68V32W95S9Q35P081TJ5ZZTJ8M5WJG"
+        println("origin: $origin")
+        val decodedNormal = decodeCrock(origin)
+        val originWithReplacedOcrProblems1 = origin
+            .replace('1', 'L')
+            .replace('V', 'U')
+            .replace('0', 'O')
+        val originWithReplacedOcrProblems2 = origin
+            .replace('1', 'I')
+            .replace('V', 'U')
+            .replace('0', 'O')
+        println("encoded 1: $originWithReplacedOcrProblems1")
+        println("encoded 2: $originWithReplacedOcrProblems2")
+        val decoded1 = decodeCrock(originWithReplacedOcrProblems1)
+        val decoded2 = decodeCrock(originWithReplacedOcrProblems2)
+
+        assert(decodedNormal.contentEquals(decoded1))
+        assert(decodedNormal.contentEquals(decoded2))
+        assert(decoded1.contentEquals(decoded2))
+    }
+
+    private fun rand32Bytes(): ByteArray {
+        val wopid = ByteArray(32)
+        val rand = SecureRandom()
+        rand.nextBytes(wopid) // will seed automatically
+        return wopid
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]