gnunet-svn
[Top][All Lists]
Advanced

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

[taler-cashless2ecash] branch master updated: docs: add feedback


From: gnunet
Subject: [taler-cashless2ecash] branch master updated: docs: add feedback
Date: Thu, 28 Mar 2024 22:10:45 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 5770318  docs: add feedback
5770318 is described below

commit 5770318d4a4f7a8e668afa7db088776191ccd191
Author: Joel-Haeberli <haebu@rubigen.ch>
AuthorDate: Thu Mar 28 22:10:29 2024 +0100

    docs: add feedback
---
 bruno/c2ec/(LOCAL-BIA) Abort Withdrawal.bru        |  11 +
 bruno/c2ec/(LOCAL-BIA) Payment Confirmation.bru    |  11 +
 bruno/c2ec/(LOCAL-BIA) Register Withdrawal.bru     |  11 +
 bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru       |  11 +
 .../(LOCAL-WIRE) Transaction History Incoming.bru  |  11 +
 bruno/c2ec/(LOCAL-WIRE) Transfer.bru               |  11 +
 bruno/c2ec/bruno.json                              |   9 +
 bruno/wallee/bruno.json                            |   9 +
 c2ec/{common => }/amount.go                        |  11 +-
 c2ec/{common => }/amount_test.go                   |   2 +-
 c2ec/bank-integration.go                           | 234 ++++++++++++----
 c2ec/c2ec-config.yaml                              |   4 +-
 c2ec/{common => }/codec.go                         |   2 +-
 c2ec/{common => }/codec_test.go                    |   5 +-
 c2ec/config.go                                     |  19 --
 c2ec/db.go                                         |  82 ++++--
 .../db/0000-c2ec_schema.sql                        |  28 +-
 c2ec/db/0000-c2ec_status_listener.sql              |  41 +++
 c2ec/db/versioning.sql                             | 294 +++++++++++++++++++++
 c2ec/go.mod                                        |  18 +-
 c2ec/go.sum                                        |  35 ++-
 c2ec/{common => }/http-util.go                     |  24 +-
 c2ec/{common => }/http-util_test.go                |  13 +-
 c2ec/{common => }/model.go                         |  22 +-
 c2ec/postgres.go                                   | 228 ++++++++++++++--
 c2ec/wire-gateway.go                               |  61 +++--
 docs/content/architecture/overview.tex             |  11 +-
 docs/content/implementation/c2ec.tex               |   5 +
 docs/content/implementation/exchange.tex           |   1 -
 docs/content/implementation/terminal.tex           |   2 +-
 docs/content/implementation/wallee.tex             |   1 -
 docs/content/implementation/wallet.tex             |   1 +
 docs/content/introduction/goal.tex                 |   6 +-
 docs/pictures/diagrams/components_image.png        | Bin 0 -> 186243 bytes
 docs/pictures/diagrams/components_images.png       | Bin 131896 -> 0 bytes
 docs/project.bib                                   |   7 +
 docs/thesis.pdf                                    | Bin 1589959 -> 1644976 
bytes
 docs/thesis.tex                                    |   4 +-
 specs/components_images.odg                        | Bin 302162 -> 295332 bytes
 39 files changed, 1041 insertions(+), 204 deletions(-)

diff --git a/bruno/c2ec/(LOCAL-BIA) Abort Withdrawal.bru 
b/bruno/c2ec/(LOCAL-BIA) Abort Withdrawal.bru
new file mode 100644
index 0000000..1dfe0ac
--- /dev/null
+++ b/bruno/c2ec/(LOCAL-BIA) Abort Withdrawal.bru       
@@ -0,0 +1,11 @@
+meta {
+  name: (LOCAL-BIA) Abort Withdrawal
+  type: http
+  seq: 5
+}
+
+post {
+  url: http://localhost:8081/c2ec/withdrawal-operation/WOPID/abort
+  body: none
+  auth: none
+}
diff --git a/bruno/c2ec/(LOCAL-BIA) Payment Confirmation.bru 
b/bruno/c2ec/(LOCAL-BIA) Payment Confirmation.bru
new file mode 100644
index 0000000..22b4d5e
--- /dev/null
+++ b/bruno/c2ec/(LOCAL-BIA) Payment Confirmation.bru   
@@ -0,0 +1,11 @@
+meta {
+  name: (LOCAL-BIA) Payment Confirmation
+  type: http
+  seq: 4
+}
+
+post {
+  url: http://localhost:8081/c2ec/withdrawal-operation/WOPID
+  body: none
+  auth: none
+}
diff --git a/bruno/c2ec/(LOCAL-BIA) Register Withdrawal.bru 
b/bruno/c2ec/(LOCAL-BIA) Register Withdrawal.bru
new file mode 100644
index 0000000..97b4a63
--- /dev/null
+++ b/bruno/c2ec/(LOCAL-BIA) Register Withdrawal.bru    
@@ -0,0 +1,11 @@
+meta {
+  name: (LOCAL-BIA) Register Withdrawal
+  type: http
+  seq: 2
+}
+
+post {
+  url: http://localhost:8081/c2ec/withdrawal-operation
+  body: none
+  auth: none
+}
diff --git a/bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru 
b/bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru
new file mode 100644
index 0000000..7de1e1b
--- /dev/null
+++ b/bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru      
@@ -0,0 +1,11 @@
+meta {
+  name: (LOCAL-BIA) Withdrawal Status
+  type: http
+  seq: 3
+}
+
+get {
+  url: http://localhost:8081/c2ec/withdrawal-operation/WOPID
+  body: none
+  auth: none
+}
diff --git a/bruno/c2ec/(LOCAL-WIRE) Transaction History Incoming.bru 
b/bruno/c2ec/(LOCAL-WIRE) Transaction History Incoming.bru
new file mode 100644
index 0000000..4c63876
--- /dev/null
+++ b/bruno/c2ec/(LOCAL-WIRE) Transaction History Incoming.bru  
@@ -0,0 +1,11 @@
+meta {
+  name: (LOCAL-WIRE) Transaction History Incoming
+  type: http
+  seq: 7
+}
+
+get {
+  url: http://localhost:8081/wire/history/incoming
+  body: none
+  auth: none
+}
diff --git a/bruno/c2ec/(LOCAL-WIRE) Transfer.bru b/bruno/c2ec/(LOCAL-WIRE) 
Transfer.bru
new file mode 100644
index 0000000..b0a5f49
--- /dev/null
+++ b/bruno/c2ec/(LOCAL-WIRE) Transfer.bru      
@@ -0,0 +1,11 @@
+meta {
+  name: (LOCAL-WIRE) Transfer
+  type: http
+  seq: 6
+}
+
+post {
+  url: http://localhost:8081/wire/transfer
+  body: none
+  auth: none
+}
diff --git a/bruno/c2ec/bruno.json b/bruno/c2ec/bruno.json
new file mode 100644
index 0000000..682e855
--- /dev/null
+++ b/bruno/c2ec/bruno.json
@@ -0,0 +1,9 @@
+{
+  "version": "1",
+  "name": "c2ec",
+  "type": "collection",
+  "ignore": [
+    "node_modules",
+    ".git"
+  ]
+}
\ No newline at end of file
diff --git a/bruno/wallee/bruno.json b/bruno/wallee/bruno.json
new file mode 100644
index 0000000..4d89738
--- /dev/null
+++ b/bruno/wallee/bruno.json
@@ -0,0 +1,9 @@
+{
+  "version": "1",
+  "name": "wallee",
+  "type": "collection",
+  "ignore": [
+    "node_modules",
+    ".git"
+  ]
+}
\ No newline at end of file
diff --git a/c2ec/common/amount.go b/c2ec/amount.go
similarity index 95%
rename from c2ec/common/amount.go
rename to c2ec/amount.go
index a79e8a3..faa0375 100644
--- a/c2ec/common/amount.go
+++ b/c2ec/amount.go
@@ -16,7 +16,7 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
-package common
+package main
 
 import (
        "errors"
@@ -40,6 +40,15 @@ type Amount struct {
        Fraction uint64 `json:"fraction"`
 }
 
+func ToAmount(amount TalerAmountCurrency) (*Amount, error) {
+
+       a := new(Amount)
+       a.Currency = amount.Curr
+       a.Value = uint64(amount.Val)
+       a.Fraction = uint64(amount.Frac)
+       return a, nil
+}
+
 // The maximim length of a fraction (in digits)
 const FractionalLength = 8
 
diff --git a/c2ec/common/amount_test.go b/c2ec/amount_test.go
similarity index 99%
rename from c2ec/common/amount_test.go
rename to c2ec/amount_test.go
index 3a3d01a..fc3cc45 100644
--- a/c2ec/common/amount_test.go
+++ b/c2ec/amount_test.go
@@ -16,7 +16,7 @@
 //
 // SPDX-License-Identifier: AGPL3.0-or-later
 
-package common
+package main
 
 import (
        "fmt"
diff --git a/c2ec/bank-integration.go b/c2ec/bank-integration.go
index c280c67..cccd039 100644
--- a/c2ec/bank-integration.go
+++ b/c2ec/bank-integration.go
@@ -2,9 +2,12 @@ package main
 
 import (
        "bytes"
-       "c2ec/common"
+       "fmt"
        "log"
        http "net/http"
+       "strconv"
+       "strings"
+       "time"
 )
 
 const BANK_INTEGRATION_CONFIG_ENDPOINT = "/config"
@@ -16,6 +19,9 @@ 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"`
@@ -36,24 +42,24 @@ type BankIntegrationConfig struct {
 }
 
 type C2ECWithdrawRegistration struct {
-       Wopid         common.WithdrawalIdentifier `json:"wopid"`
-       ReservePubKey common.EddsaPublicKey       `json:"reserve_pub_key"`
-       Amount        common.Amount               `json:"amount"`
-       ProviderId    uint64                      `json:"provider_id"`
+       Wopid         WithdrawalIdentifier `json:"wopid"`
+       ReservePubKey EddsaPublicKey       `json:"reserve_pub_key"`
+       Amount        Amount               `json:"amount"`
+       TerminalId    uint64               `json:"terminal_id"`
 }
 
 type C2ECWithdrawalStatus struct {
-       Status        common.WithdrawalOperationStatus `json:"status"`
-       Amount        common.Amount                    `json:"amount"`
-       SenderWire    string                           `json:"sender_wire"`
-       WireTypes     []string                         `json:"wire_types"`
-       ReservePubKey common.EddsaPublicKey            
`json:"selected_reserve_pub"`
+       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"`
-       Amount                common.Amount `json:"amount"`
-       Fees                  common.Amount `json:"fees"`
+       ProviderTransactionId string `json:"provider_transaction_id"`
+       Amount                Amount `json:"amount"`
+       Fees                  Amount `json:"fees"`
 }
 
 func bankIntegrationConfig(res http.ResponseWriter, req *http.Request) {
@@ -63,31 +69,31 @@ func bankIntegrationConfig(res http.ResponseWriter, req 
*http.Request) {
                Version: "0:0:1",
        }
 
-       serializedCfg, err := 
common.NewJsonCodec[BankIntegrationConfig]().EncodeToBytes(&cfg)
+       serializedCfg, err := 
NewJsonCodec[BankIntegrationConfig]().EncodeToBytes(&cfg)
        if err != nil {
                log.Default().Printf("failed serializing config: %s", 
err.Error())
-               res.WriteHeader(common.HTTP_INTERNAL_SERVER_ERROR)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
-       res.WriteHeader(common.HTTP_OK)
+       res.WriteHeader(HTTP_OK)
        res.Write(serializedCfg)
 }
 
 func handleWithdrawalRegistration(res http.ResponseWriter, req *http.Request) {
 
-       jsonCodec := common.NewJsonCodec[C2ECWithdrawRegistration]()
-       registration, err := 
common.ReadStructFromBody[C2ECWithdrawRegistration](req, jsonCodec)
+       jsonCodec := NewJsonCodec[C2ECWithdrawRegistration]()
+       registration, err := ReadStructFromBody[C2ECWithdrawRegistration](req, 
jsonCodec)
        if err != nil {
 
-               err := common.WriteProblem(res, common.HTTP_BAD_REQUEST, 
&common.RFC9457Problem{
-                       TypeUri:  common.TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAW_REGISTRATION_INVALID_REQ",
+               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(common.HTTP_INTERNAL_SERVER_ERROR)
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                }
                return
        }
@@ -95,58 +101,192 @@ func handleWithdrawalRegistration(res 
http.ResponseWriter, req *http.Request) {
        err = DB.RegisterWithdrawal(registration)
        if err != nil {
 
-               err := common.WriteProblem(res, 
common.HTTP_INTERNAL_SERVER_ERROR, &common.RFC9457Problem{
-                       TypeUri:  common.TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAW_REGISTRATION_DB_FAILURE",
-                       Title:    "databse failure",
+               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(common.HTTP_INTERNAL_SERVER_ERROR)
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                }
                return
        }
 
-       res.WriteHeader(common.HTTP_NO_CONTENT)
+       res.WriteHeader(HTTP_NO_CONTENT)
 }
 
 // Get status of withdrawal associated with the given WOPID
 //
+// # If the
+//
 // Parameters:
 //   - long_poll_ms (optional):
 //     milliseconds to wait for state to change
 //     given old_state until responding
 //   - old_state (optional):
 //     Default is 'pending'
-//   - terminal_provider_id (optional):
+//   - provider_id (optional, for c2ec use mandatory):
 //     The terminal provider requesting for status update.
 func handleWithdrawalStatus(res http.ResponseWriter, req *http.Request) {
 
+       // read and validate request query parameters
+       longPollMilli := DEFAULT_LONG_POLL_MS
+       longPollMilliPtr, err := OptionalQueryParamOrError("long_poll_ms", req, 
strconv.Atoi)
+       if err != nil {
+               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_INVALID_PARAMETER",
+                       Title:    "invalid request parameter",
+                       Detail:   "the withdrawal status request parameter 
'long_poll_ms' is malformed (error: " + err.Error() + ")",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+       if longPollMilliPtr != nil {
+               longPollMilli = *longPollMilliPtr
+       }
+
+       oldState := DEFAULT_OLD_STATE
+       oldStatePtr, err := OptionalQueryParamOrError("old_state", req, 
ToWithdrawalOpStatus)
+       if err != nil {
+               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_INVALID_PARAMETER",
+                       Title:    "invalid request parameter",
+                       Detail:   "the withdrawal status request parameter 
'old_state' is malformed (error: " + err.Error() + ")",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+       if oldStatePtr != nil {
+               oldState = *oldStatePtr
+       }
+
+       // TODO is this needed ? I think there's a better solution
+       // providerId, err := OptionalQueryParamOrError("provider_id", req, 
strconv.Atoi)
+       // if err != nil {
+       //      err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
+       //              TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_INVALID_PARAMETER",
+       //              Title:    "invalid request parameter",
+       //              Detail:   "the withdrawal status request parameter 
'provider_id' 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)
        if wopid == "" {
-               res.WriteHeader(common.HTTP_BAD_REQUEST)
+               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_INVALID_PARAMETER",
+                       Title:    "invalid request path parameter",
+                       Detail:   "the withdrawal status request path parameter 
'wopid' is malformed (error: " + err.Error() + ")",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+
+       // 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:   "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
        }
 
-       res.WriteHeader(common.HTTP_OK)
-       res.Write(bytes.NewBufferString("retrieved withdrawal status request 
for wopid=" + wopid).Bytes())
+       // if the old state was supplied and the current status of withdrawal is
+       // different, return the current withdrawal directly.
+       if oldStatePtr != nil {
+               // Only enter listening mode if the old state did not yet change
+               // compared to the current state.
+               if strings.EqualFold(string(oldState), 
string(withdrawal.WithdrawalStatus)) {
+                       // Listen for change from old_state here for a maximal 
time of long_poll_ms
+                       duration := time.Duration(longPollMilli) * 
time.Millisecond
+                       withdrawal, err = DB.AwaitWithdrawalStatusChange(wopid, 
duration, oldState)
+                       if err != nil {
+                               err := WriteProblem(res, HTTP_NOT_FOUND, 
&RFC9457Problem{
+                                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_WITHDRAWAL_STATUS_LISTEN_FOR_CHANGE_FAILED",
+                                       Title:    fmt.Sprintf("failed while 
listening for change of status '%s' for withdrawal-operation with wopid=%s", 
oldState, wopid),
+                                       Detail:   "listening for 
C2ECWithdrawalStatus object failed (error:" + err.Error() + ")",
+                                       Instance: req.RequestURI,
+                               })
+                               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: req.RequestURI,
+               })
+               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: req.RequestURI,
+                       })
+                       if err != nil {
+                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+                       }
+                       return
+               }
+               res.WriteHeader(HTTP_OK)
+               res.Write(withdrawalStatusBytes)
+               return
+       }
 }
 
 func handlePaymentNotification(res http.ResponseWriter, req *http.Request) {
 
        wopid := req.PathValue(WOPID_PARAMETER)
        if wopid == "" {
-               res.WriteHeader(common.HTTP_BAD_REQUEST)
+               res.WriteHeader(HTTP_BAD_REQUEST)
                return
        }
 
-       res.WriteHeader(common.HTTP_OK)
+       res.WriteHeader(HTTP_OK)
        res.Write(bytes.NewBufferString("retrieved payment notification for 
wopid=" + wopid).Bytes())
 }
 
 func handleWithdrawalAbort(res http.ResponseWriter, req *http.Request) {
 
-       res.WriteHeader(common.HTTP_OK)
+       res.WriteHeader(HTTP_OK)
        res.Write(bytes.NewBufferString("retrieved withdrawal operation 
abortion request").Bytes())
 }
 
@@ -161,22 +301,22 @@ type BankWithdrawalOperationPostRequest struct {
 
 // 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankWithdrawalOperationPostResponse
 type BankWithdrawalOperationPostResponse struct {
-       Status             common.WithdrawalOperationStatus `json:"status"`
-       ConfirmTransferUrl string                           
`json:"confirm_transfer_url"`
-       TransferDone       bool                             
`json:"transfer_done"`
+       Status             WithdrawalOperationStatus `json:"status"`
+       ConfirmTransferUrl string                    
`json:"confirm_transfer_url"`
+       TransferDone       bool                      `json:"transfer_done"`
 }
 
 // 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankWithdrawalOperationStatus
 type BankWithdrawalOperationStatus struct {
-       Status                  common.WithdrawalOperationStatus `json:"status"`
-       Amount                  common.Amount                    `json:"amount"`
-       SenderWire              string                           
`json:"sender_wire"`
-       SuggestedExchange       string                           
`json:"suggested_exchange"`
-       ConfirmTransferUrl      string                           
`json:"confirm_transfer_url"`
-       WireTypes               []string                         
`json:"wire_types"`
-       SelectedReservePub      string                           
`json:"selected_reserve_pub"`
-       SelectedExchangeAccount string                           
`json:"selected_exchange_account"`
-       Aborted                 bool                             
`json:"aborted"`
-       SelectionDone           bool                             
`json:"selection_done"`
-       TransferDone            bool                             
`json:"transfer_done"`
+       Status                  WithdrawalOperationStatus `json:"status"`
+       Amount                  Amount                    `json:"amount"`
+       SenderWire              string                    `json:"sender_wire"`
+       SuggestedExchange       string                    
`json:"suggested_exchange"`
+       ConfirmTransferUrl      string                    
`json:"confirm_transfer_url"`
+       WireTypes               []string                  `json:"wire_types"`
+       SelectedReservePub      string                    
`json:"selected_reserve_pub"`
+       SelectedExchangeAccount string                    
`json:"selected_exchange_account"`
+       Aborted                 bool                      `json:"aborted"`
+       SelectionDone           bool                      
`json:"selection_done"`
+       TransferDone            bool                      `json:"transfer_done"`
 }
diff --git a/c2ec/c2ec-config.yaml b/c2ec/c2ec-config.yaml
index 8692007..4e3d50d 100644
--- a/c2ec/c2ec-config.yaml
+++ b/c2ec/c2ec-config.yaml
@@ -6,5 +6,5 @@ c2ec:
 db:
   host: "localhost"
   port: 5432
-  username: "user"
-  password: "password"
+  username: "local"
+  password: "local"
diff --git a/c2ec/common/codec.go b/c2ec/codec.go
similarity index 98%
rename from c2ec/common/codec.go
rename to c2ec/codec.go
index 0f658ee..df4dbcd 100644
--- a/c2ec/common/codec.go
+++ b/c2ec/codec.go
@@ -1,4 +1,4 @@
-package common
+package main
 
 import (
        "bytes"
diff --git a/c2ec/common/codec_test.go b/c2ec/codec_test.go
similarity index 93%
rename from c2ec/common/codec_test.go
rename to c2ec/codec_test.go
index 43962c2..f167655 100644
--- a/c2ec/common/codec_test.go
+++ b/c2ec/codec_test.go
@@ -1,8 +1,7 @@
-package common_test
+package main
 
 import (
        "bytes"
-       "c2ec/common"
        "fmt"
 
        "testing"
@@ -37,7 +36,7 @@ func TestJsonCodecRoundTrip(t *testing.T) {
                },
        }
 
-       jsonCodec := new(common.JsonCodec[TestStruct])
+       jsonCodec := new(JsonCodec[TestStruct])
 
        encodedTestObj, err := jsonCodec.Encode(&testObj)
        if err != nil {
diff --git a/c2ec/config.go b/c2ec/config.go
index 4a85996..5a6b8d1 100644
--- a/c2ec/config.go
+++ b/c2ec/config.go
@@ -2,8 +2,6 @@ package main
 
 import (
        "os"
-       "strconv"
-       "strings"
 
        "gopkg.in/yaml.v3"
 )
@@ -57,20 +55,3 @@ func Parse(path string) (*C2ECConfig, error) {
 
        return cfg, nil
 }
-
-func DBConnectionString(cfg *C2ECDatabseConfig) string {
-
-       // format: postgres://username:password@hostname:port/database_name
-       return strings.Join([]string{
-               POSTGRESQL_SCHEME,
-               cfg.Username,
-               ":",
-               cfg.Password,
-               "@",
-               cfg.Host,
-               ":",
-               strconv.FormatInt(int64(cfg.Port), 10),
-               "/",
-               NONCE2ECASH_DATABASE,
-       }, "")
-}
diff --git a/c2ec/db.go b/c2ec/db.go
index 866800e..deda5d1 100644
--- a/c2ec/db.go
+++ b/c2ec/db.go
@@ -1,41 +1,68 @@
 package main
 
-import "c2ec/common"
+import "time"
 
-type TerminalProvider struct {
-       ProviderTerminalID int64
-       Name               string
-       BackendBaseURL     string
-       BackendCredentials string
+const PROVIDER_TABLE_NAME = "provider"
+const PROVIDER_FIELD_NAME_ID = "terminal_id"
+const PROVIDER_FIELD_NAME_NAME = "name"
+const PROVIDER_FIELD_NAME_BACKEND_URL = "backend_base_url"
+const PROVIDER_FIELD_NAME_BACKEND_CREDENTIALS = "backend_credentials"
+
+const TERMINAL_TABLE_NAME = "terminal"
+const TERMINAL_FIELD_NAME_ID = "terminal_id"
+const TERMINAL_FIELD_NAME_ACCESS_TOKEN = "access_token"
+const TERMINAL_FIELD_NAME_ACTIVE = "active"
+const TERMINAL_FIELD_NAME_DESCRIPTION = "description"
+const TERMINAL_FIELD_NAME_PROVIDER_ID = "provider_id"
+
+const WITHDRAWAL_TABLE_NAME = "withdrawal"
+const WITHDRAWAL_FIELD_NAME_ID = "withdrawal_id"
+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_STATUS = "withdrawal_status"
+const WITHDRAWAL_FIELD_NAME_TERMINAL_ID = "terminal_id"
+const WITHDRAWAL_FIELD_NAME_TRANSACTION_ID = "provider_transaction_id"
+const WITHDRAWAL_FIELD_NAME_LAST_RETRY = "last_retry_ts"
+const WITHDRAWAL_FIELD_NAME_RETRY_COUNTER = "retry_counter"
+const WITHDRAWAL_FIELD_NAME_COMPLETION_PROOF = "completion_proof"
+
+type Provider struct {
+       ProviderTerminalID int64  `db:"provider_id"`
+       Name               string `db:"name"`
+       BackendBaseURL     string `db:"backend_base_url"`
+       BackendCredentials string `db:"backend_credentials"`
 }
 
 type Terminal struct {
-       TerminalID  int64
-       AccessToken []byte
-       Active      bool
-       ProviderID  int64
-       Provider    TerminalProvider
-       Description string
+       TerminalID  int64  `db:"terminal_id"`
+       AccessToken []byte `db:"access_token"`
+       Active      bool   `db:"active"`
+       Description string `db:"description"`
+       ProviderID  int64  `db:"provider_id"`
 }
 
 type Withdrawal struct {
-       WithdrawalId          []byte
-       ReservePubKey         []byte
-       RegistrationTs        int64
-       Amount                TalerAmountCurrency
-       Fees                  TalerAmountCurrency
-       WithdrawalStatus      common.WithdrawalOperationStatus
-       TerminalId            int64
-       ProviderTransactionId string
-       LastRetryTs           int64
-       RetryCounter          int32
-       CompletionProof       []byte
+       WithdrawalId          []byte                    `db:"withdrawal_id"`
+       Wopid                 uint64                    `db:"wopid"`
+       ReservePubKey         []byte                    `db:"reserve_pub_key"`
+       RegistrationTs        int64                     `db:"registration_ts"`
+       Amount                TalerAmountCurrency       `db:"amount"`
+       Fees                  TalerAmountCurrency       `db:"fees"`
+       WithdrawalStatus      WithdrawalOperationStatus `db:"withdrawal_status"`
+       TerminalId            int64                     `db:"terminal_id"`
+       ProviderTransactionId string                    
`db:"provider_transaction_id"`
+       LastRetryTs           int64                     `db:"last_retry_ts"`
+       RetryCounter          int32                     `db:"retry_counter"`
+       CompletionProof       []byte                    `db:"completion_proof"`
 }
 
 type TalerAmountCurrency struct {
-       Val  int64
-       Frac int32
-       Curr string
+       Val  int64  `db:"val"`
+       Frac int32  `db:"frac"`
+       Curr string `db:"curr"`
 }
 
 type C2ECDatabase interface {
@@ -43,6 +70,7 @@ type C2ECDatabase interface {
        GetWithdrawalByWopid(wopid string) (*Withdrawal, error)
        ConfirmPayment(c *C2ECPaymentNotification) error
        GetUnconfirmedWithdrawals() ([]*Withdrawal, error)
-       GetTerminalProviderById(id int) (*TerminalProvider, error)
+       GetTerminalProviderById(id int) (*Provider, error)
        GetTerminalById(id int) (*Terminal, error)
+       AwaitWithdrawalStatusChange(wopid string, duration time.Duration, 
oldState WithdrawalOperationStatus) (*Withdrawal, error)
 }
diff --git a/data/c2ec_schema.sql b/c2ec/db/0000-c2ec_schema.sql
similarity index 87%
rename from data/c2ec_schema.sql
rename to c2ec/db/0000-c2ec_schema.sql
index b327c0e..75f1f0e 100644
--- a/data/c2ec_schema.sql
+++ b/c2ec/db/0000-c2ec_schema.sql
@@ -1,4 +1,7 @@
---  => proper versioning.sql nehmen (siehe exchange.git),
+BEGIN;
+
+SELECT _v.register_patch('0000-c2ec-schema', NULL, NULL);
+
 DROP SCHEMA IF EXISTS c2ec CASCADE;
 
 CREATE SCHEMA c2ec;
@@ -29,21 +32,21 @@ COMMENT ON TYPE taler_amount_currency
   copied from 
https://git.taler.net/merchant.git/tree/src/backenddb/merchant-0001.sql';
 
 
-CREATE TABLE IF NOT EXISTS terminal_provider (
-    provider_terminal_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+CREATE TABLE IF NOT EXISTS provider (
+    provider_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
     name TEXT NOT NULL UNIQUE,
     backend_base_url TEXT NOT NULL,
     backend_credentials TEXT NOT NULL
 );
-COMMENT ON TABLE terminal_provider
+COMMENT ON TABLE provider
   IS 'Table describing providers of c2ec terminal';
-COMMENT ON COLUMN terminal_provider.provider_terminal_id
+COMMENT ON COLUMN provider.provider_id
   IS 'Uniquely identifies a provider';
-COMMENT ON COLUMN terminal_provider.name
+COMMENT ON COLUMN provider.name
   IS 'Name of the provider, used for selection in transaction proofing';
-COMMENT ON COLUMN terminal_provider.backend_base_url
+COMMENT ON COLUMN provider.backend_base_url
   IS 'URL of the provider backend for transaction proofing';
-COMMENT ON COLUMN terminal_provider.backend_credentials
+COMMENT ON COLUMN provider.backend_credentials
   IS 'Credentials used to access the backend of the provider';
 
 
@@ -52,7 +55,7 @@ CREATE TABLE IF NOT EXISTS terminal (
     access_token BYTEA CHECK (LENGTH(access_token)=32) NOT NULL,
     active BOOLEAN NOT NULL DEFAULT TRUE,
     description TEXT,
-    provider_id INT8 NOT NULL REFERENCES 
terminal_provider(provider_terminal_id)
+    provider_id INT8 NOT NULL REFERENCES provider(provider_id)
 );
 COMMENT ON TABLE terminal
   IS 'Table containing information about terminals of providers';
@@ -109,3 +112,10 @@ COMMENT ON COLUMN withdrawal.retry_counter
   IS 'Number of retry attempts';
 COMMENT ON COLUMN withdrawal.completion_proof
   IS 'Proof of transaction upon final completion delivered by the providers 
system';
+
+CREATE INDEX wopid_index ON withdrawal (wopid);
+COMMENT ON INDEX wopid_index
+  IS 'The wopid is the search key for each bank-integration api related 
request.
+  Thus it makes sense to create an index on the column.';
+
+COMMIT;
diff --git a/c2ec/db/0000-c2ec_status_listener.sql 
b/c2ec/db/0000-c2ec_status_listener.sql
new file mode 100644
index 0000000..8516767
--- /dev/null
+++ b/c2ec/db/0000-c2ec_status_listener.sql
@@ -0,0 +1,41 @@
+BEGIN;
+
+SELECT _v.register_patch('0000-c2ec-status-listener', 
ARRAY['0000-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('withdrawal_' || NEW.withdrawal_id, 
NEW.withdrawal_status::TEXT);
+    RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+COMMENT ON FUNCTION emit_withdrawal_status 
+       IS 'The function selects the withdrawal according to the wopid 
+       of the functions argument and sends a notification on the channel 
+       "withdrawal-{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;
diff --git a/c2ec/db/versioning.sql b/c2ec/db/versioning.sql
new file mode 100644
index 0000000..444cf95
--- /dev/null
+++ b/c2ec/db/versioning.sql
@@ -0,0 +1,294 @@
+-- LICENSE AND COPYRIGHT
+--
+-- Copyright (C) 2010 Hubert depesz Lubaczewski
+--
+-- This program is distributed under the (Revised) BSD License:
+-- L<http://www.opensource.org/licenses/bsd-license.php>
+--
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions
+-- are met:
+--
+-- * Redistributions of source code must retain the above copyright
+-- notice, this list of conditions and the following disclaimer.
+--
+-- * Redistributions in binary form must reproduce the above copyright
+--   notice, this list of conditions and the following disclaimer in the
+--   documentation and/or other materials provided with the distribution.
+--
+-- * Neither the name of Hubert depesz Lubaczewski's Organization
+--   nor the names of its contributors may be used to endorse or
+--   promote products derived from this software without specific
+--   prior written permission.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE
+-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY,
+-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
USE
+-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--
+-- Code origin: 
https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql
+--
+--
+-- # NAME
+--
+-- **Versioning** - simplistic take on tracking and applying changes to 
databases.
+--
+-- # DESCRIPTION
+--
+-- This project strives to provide simple way to manage changes to
+-- database.
+--
+-- Instead of making changes on development server, then finding
+-- differences between production and development, deciding which ones
+-- should be installed on production, and finding a way to install them -
+-- you start with writing diffs themselves!
+--
+-- # INSTALLATION
+--
+-- To install versioning simply run install.versioning.sql in your database
+-- (all of them: production, stage, test, devel, ...).
+--
+-- # USAGE
+--
+-- In your files with patches to database, put whole logic in single
+-- transaction, and use \_v.\* functions - usually \_v.register_patch() at
+-- least to make sure everything is OK.
+--
+-- For example. Let's assume you have patch files:
+--
+-- ## 0001.sql:
+--
+-- ```
+-- create table users (id serial primary key, username text);
+-- ```
+--
+-- ## 0002.sql:
+--
+-- ```
+-- insert into users (username) values ('depesz');
+-- ```
+-- To change it to use versioning you would change the files, to this
+-- state:
+--
+-- 0000.sql:
+--
+-- ```
+-- BEGIN;
+-- select _v.register_patch('000-base', NULL, NULL);
+-- create table users (id serial primary key, username text);
+-- COMMIT;
+-- ```
+--
+-- ## 0002.sql:
+--
+-- ```
+-- BEGIN;
+-- select _v.register_patch('001-users', ARRAY['000-base'], NULL);
+-- insert into users (username) values ('depesz');
+-- COMMIT;
+-- ```
+--
+-- This will make sure that patch 001-users can only be applied after
+-- 000-base.
+--
+-- # AVAILABLE FUNCTIONS
+--
+-- ## \_v.register_patch( TEXT )
+--
+-- Registers named patch, or dies if it is already registered.
+--
+-- Returns integer which is id of patch in \_v.patches table - only if it
+-- succeeded.
+--
+-- ## \_v.register_patch( TEXT, TEXT[] )
+--
+-- Same as \_v.register_patch( TEXT ), but checks is all given patches (given 
as
+-- array in second argument) are already registered.
+--
+-- ## \_v.register_patch( TEXT, TEXT[], TEXT[] )
+--
+-- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no 
conflicts with preexisting patches.
+--
+-- Third argument is array of names of patches that conflict with current one. 
So
+-- if any of them is installed - register_patch will error out.
+--
+-- ## \_v.unregister_patch( TEXT )
+--
+-- Removes information about given patch from the versioning data.
+--
+-- It doesn't remove objects that were created by this patch - just removes
+-- metainformation.
+--
+-- ## \_v.assert_user_is_superuser()
+--
+-- Make sure that current patch is being loaded by superuser.
+--
+-- If it's not - it will raise exception, and break transaction.
+--
+-- ## \_v.assert_user_is_not_superuser()
+--
+-- Make sure that current patch is not being loaded by superuser.
+--
+-- If it is - it will raise exception, and break transaction.
+--
+-- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... )
+--
+-- Make sure that current patch is being loaded by one of listed users.
+--
+-- If ```current_user``` is not listed as one of arguments - function will 
raise
+-- exception and break the transaction.
+
+BEGIN;
+
+
+-- This file adds versioning support to database it will be loaded to.
+-- It requires that PL/pgSQL is already loaded - will raise exception 
otherwise.
+-- All versioning "stuff" (tables, functions) is in "_v" schema.
+
+-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them 
to RETURN literally nothing (0 rows).
+-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql 
when calling
+CREATE SCHEMA IF NOT EXISTS _v;
+COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
+
+CREATE TABLE IF NOT EXISTS _v.patches (
+    patch_name  TEXT        PRIMARY KEY,
+    applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(),
+    applied_by  TEXT        NOT NULL,
+    requires    TEXT[],
+    conflicts   TEXT[]
+);
+COMMENT ON TABLE _v.patches              IS 'Contains information about what 
patches are currently applied on database.';
+COMMENT ON COLUMN _v.patches.patch_name  IS 'Name of patch, has to be unique 
for every patch.';
+COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.';
+COMMENT ON COLUMN _v.patches.applied_by  IS 'Who applied this patch 
(PostgreSQL username)';
+COMMENT ON COLUMN _v.patches.requires    IS 'List of patches that are required 
for given patch.';
+COMMENT ON COLUMN _v.patches.conflicts   IS 'List of patches that conflict 
with given patch.';
+
+CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN 
in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS 
setof INT4 AS $$
+DECLARE
+    t_text   TEXT;
+    t_text_a TEXT[];
+    i INT4;
+BEGIN
+    -- Thanks to this we know only one patch will be applied at a time
+    LOCK TABLE _v.patches IN EXCLUSIVE MODE;
+
+    SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = 
in_patch_name;
+    IF FOUND THEN
+        RAISE EXCEPTION 'Patch % is already applied!', in_patch_name;
+    END IF;
+
+    t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = 
any( in_conflicts ) );
+    IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+        RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) 
installed: %.', array_to_string( t_text_a, ', ' );
+    END IF;
+
+    IF array_upper( in_requirements, 1 ) IS NOT NULL THEN
+        t_text_a := '{}';
+        FOR i IN array_lower( in_requirements, 1 ) .. array_upper( 
in_requirements, 1 ) LOOP
+            SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = 
in_requirements[i];
+            IF NOT FOUND THEN
+                t_text_a := t_text_a || in_requirements[i];
+            END IF;
+        END LOOP;
+        IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+            RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( 
t_text_a, ', ' );
+        END IF;
+    END IF;
+
+    INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, 
conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( 
in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) );
+    RETURN;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to 
register patches in database. Raises exception if there are conflicts, 
prerequisites are not installed or the migration has already been installed.';
+
+CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof 
INT4 AS $$
+    SELECT _v.register_patch( $1, $2, NULL );
+$$ language sql;
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow 
registration of patches without conflicts.';
+CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$
+    SELECT _v.register_patch( $1, NULL, NULL );
+$$ language sql;
+COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow 
registration of patches without requirements and conflicts.';
+
+CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT 
versioning INT4 ) RETURNS setof INT4 AS $$
+DECLARE
+    i        INT4;
+    t_text_a TEXT[];
+BEGIN
+    -- Thanks to this we know only one patch will be applied at a time
+    LOCK TABLE _v.patches IN EXCLUSIVE MODE;
+
+    t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = 
ANY( requires ) );
+    IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+        RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', 
in_patch_name, array_to_string( t_text_a, ', ' );
+    END IF;
+
+    DELETE FROM _v.patches WHERE patch_name = in_patch_name;
+    GET DIAGNOSTICS i = ROW_COUNT;
+    IF i < 1 THEN
+        RAISE EXCEPTION 'Patch % is not installed, so it can''t be 
uninstalled!', in_patch_name;
+    END IF;
+
+    RETURN;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister 
patches in database. Dies if the patch is not registered, or if unregistering 
it would break dependencies.';
+
+CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) 
RETURNS TEXT as $$
+DECLARE
+    t_text TEXT;
+BEGIN
+    SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = 
in_patch_name;
+    IF NOT FOUND THEN
+        RAISE EXCEPTION 'Patch % is not applied!', in_patch_name;
+    END IF;
+    RETURN format('Patch %s is applied.', in_patch_name);
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can 
be used to make sure that patch has been applied.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$
+DECLARE
+    v_super bool;
+BEGIN
+    SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
+    IF v_super THEN
+        RETURN 'assert_user_is_superuser: OK';
+    END IF;
+    RAISE EXCEPTION 'Current user is not superuser - cannot continue.';
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be 
used to make sure that patch is being applied using superuser account.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$
+DECLARE
+    v_super bool;
+BEGIN
+    SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
+    IF v_super THEN
+        RAISE EXCEPTION 'Current user is superuser - cannot continue.';
+    END IF;
+    RETURN 'assert_user_is_not_superuser: OK';
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be 
used to make sure that patch is being applied using normal (not superuser) 
account.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC 
p_acceptable_users TEXT[] ) RETURNS TEXT as $$
+DECLARE
+BEGIN
+    IF current_user = any( p_acceptable_users ) THEN
+        RETURN 'assert_user_is_one_of: OK';
+    END IF;
+    RAISE EXCEPTION 'User is not one of: % - cannot continue.', 
p_acceptable_users;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be 
used to make sure that patch is being applied by one of defined users.';
+
+COMMIT;
diff --git a/c2ec/go.mod b/c2ec/go.mod
index 8dc7a99..f90d278 100644
--- a/c2ec/go.mod
+++ b/c2ec/go.mod
@@ -2,20 +2,20 @@ module c2ec
 
 go 1.22.0
 
-require gotest.tools/v3 v3.5.1
+require (
+       github.com/jackc/pgx/v5 v5.5.5
+       gopkg.in/yaml.v3 v3.0.1
+       gotest.tools/v3 v3.5.1
+)
 
 require (
        github.com/google/go-cmp v0.5.9 // indirect
        github.com/jackc/pgpassfile v1.0.0 // indirect
        github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // 
indirect
-       github.com/jackc/pgx v3.6.2+incompatible // indirect
-       github.com/jackc/pgx/v5 v5.5.5 // indirect
-       github.com/jinzhu/inflection v1.0.0 // indirect
-       github.com/jinzhu/now v1.1.5 // indirect
-       github.com/lib/pq v1.10.9 // indirect
-       github.com/pkg/errors v0.9.1 // indirect
+       github.com/jackc/puddle/v2 v2.2.1 // indirect
+       github.com/kr/text v0.2.0 // indirect
+       github.com/rogpeppe/go-internal v1.6.1 // indirect
        golang.org/x/crypto v0.17.0 // indirect
+       golang.org/x/sync v0.1.0 // indirect
        golang.org/x/text v0.14.0 // indirect
-       gopkg.in/yaml.v3 v3.0.1 // indirect
-       gorm.io/gorm v1.25.7 // indirect
 )
diff --git a/c2ec/go.sum b/c2ec/go.sum
index c967af9..2bc4fc7 100644
--- a/c2ec/go.sum
+++ b/c2ec/go.sum
@@ -1,35 +1,46 @@
+github.com/creack/pty v1.1.9/go.mod 
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/jackc/pgpassfile v1.0.0 
h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod 
h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a 
h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod 
h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx v3.6.2+incompatible 
h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
-github.com/jackc/pgx v3.6.2+incompatible/go.mod 
h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
 github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
 github.com/jackc/pgx/v5 v5.5.5/go.mod 
h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
-github.com/jinzhu/inflection v1.0.0 
h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
-github.com/jinzhu/inflection v1.0.0/go.mod 
h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
-github.com/jinzhu/now v1.1.5/go.mod 
h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
-github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
-github.com/lib/pq v1.10.9/go.mod 
h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/jackc/puddle/v2 v2.2.1 
h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/jackc/puddle/v2 v2.2.1/go.mod 
h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/kr/pretty v0.1.0/go.mod 
h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod 
h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod 
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod 
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.6.1 
h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/rogpeppe/go-internal v1.6.1/go.mod 
h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0/go.mod 
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.0/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.1 
h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod 
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
 golang.org/x/crypto v0.17.0/go.mod 
h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod 
h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod 
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
-gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
 gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
 gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
diff --git a/c2ec/common/http-util.go b/c2ec/http-util.go
similarity index 91%
rename from c2ec/common/http-util.go
rename to c2ec/http-util.go
index 5aabc4d..37dcab5 100644
--- a/c2ec/common/http-util.go
+++ b/c2ec/http-util.go
@@ -1,4 +1,4 @@
-package common
+package main
 
 import (
        "bytes"
@@ -42,6 +42,28 @@ func WriteProblem(res http.ResponseWriter, status int, 
problem *RFC9457Problem)
        return nil
 }
 
+// The function parses a parameter of the query
+// of the request. If the parameter is not present
+// (empty string) it will not create an error and
+// just return nil.
+func OptionalQueryParamOrError[T any](
+       name string,
+       req *http.Request,
+       transform func(s string) (T, error),
+) (*T, error) {
+
+       paramStr := req.URL.Query().Get(name)
+       if paramStr != "" {
+
+               if t, err := transform(paramStr); err != nil {
+                       return nil, err
+               } else {
+                       return &t, nil
+               }
+       }
+       return nil, nil
+}
+
 // Reads a generic argument struct from the requests
 // body. It takes the codec as argument which is used to
 // decode the struct from the request. If an error occurs
diff --git a/c2ec/common/http-util_test.go b/c2ec/http-util_test.go
similarity index 79%
rename from c2ec/common/http-util_test.go
rename to c2ec/http-util_test.go
index 6b8379c..23cff4c 100644
--- a/c2ec/common/http-util_test.go
+++ b/c2ec/http-util_test.go
@@ -1,7 +1,6 @@
-package common_test
+package main
 
 import (
-       "c2ec/common"
        "fmt"
        "testing"
 )
@@ -18,13 +17,13 @@ type TestStruct struct {
 
 func TestGET(t *testing.T) {
 
-       res, status, err := common.HttpGet(
+       res, status, err := HttpGet(
                URL_GET,
                map[string]string{
                        "id": "1",
                },
                map[string]string{},
-               common.NewJsonCodec[TestStruct](),
+               NewJsonCodec[TestStruct](),
        )
 
        if err != nil {
@@ -37,7 +36,7 @@ func TestGET(t *testing.T) {
 
 func TestPOST(t *testing.T) {
 
-       res, status, err := common.HttpPost(
+       res, status, err := HttpPost(
                URL_POST,
                map[string]string{
                        "id": "1",
@@ -49,8 +48,8 @@ func TestPOST(t *testing.T) {
                        Title:     "TEST",
                        Completed: false,
                },
-               common.NewJsonCodec[TestStruct](),
-               common.NewJsonCodec[TestStruct](),
+               NewJsonCodec[TestStruct](),
+               NewJsonCodec[TestStruct](),
        )
 
        if err != nil {
diff --git a/c2ec/common/model.go b/c2ec/model.go
similarity index 84%
rename from c2ec/common/model.go
rename to c2ec/model.go
index 6b08af3..8539383 100644
--- a/c2ec/common/model.go
+++ b/c2ec/model.go
@@ -1,4 +1,9 @@
-package common
+package main
+
+import (
+       "errors"
+       "fmt"
+)
 
 // https://docs.taler.net/core/api-common.html#hash-codes
 type WithdrawalIdentifier string
@@ -30,6 +35,21 @@ const (
        CONFIRMED WithdrawalOperationStatus = "confirmed"
 )
 
+func ToWithdrawalOpStatus(s string) (WithdrawalOperationStatus, error) {
+       switch s {
+       case string(PENDING):
+               return PENDING, nil
+       case string(SELECTED):
+               return SELECTED, nil
+       case string(ABORTED):
+               return ABORTED, nil
+       case string(CONFIRMED):
+               return CONFIRMED, nil
+       default:
+               return "", errors.New(fmt.Sprintf("unknown withdrawal operation 
status '%s'", s))
+       }
+}
+
 type ErrorDetail struct {
 
        // Numeric error code unique to the condition.
diff --git a/c2ec/postgres.go b/c2ec/postgres.go
index e05e964..99857e6 100644
--- a/c2ec/postgres.go
+++ b/c2ec/postgres.go
@@ -1,52 +1,240 @@
 package main
 
 import (
+       "context"
        "errors"
        "time"
 
-       pgx "github.com/jackc/pgx"
+       "github.com/jackc/pgx/v5"
+       "github.com/jackc/pgx/v5/pgconn"
+       "github.com/jackc/pgx/v5/pgxpool"
 )
 
-const PS_INSERT_WITHDRAWAL = "INSERT INTO withdrawal " +
-       "(wopid, reserve_pub_key, registration_ts, amount, terminal_id)" +
-       " VALUES ($1, $2, $3, $4, $5)"
+const PS_INSERT_WITHDRAWAL = "INSERT INTO " + WITHDRAWAL_TABLE_NAME + "  (" +
+       WITHDRAWAL_FIELD_NAME_WOPID + "," +
+       WITHDRAWAL_FIELD_NAME_RESPUBKEY + "," +
+       WITHDRAWAL_FIELD_NAME_STATUS + "," +
+       WITHDRAWAL_FIELD_NAME_TS + "," +
+       WITHDRAWAL_FIELD_NAME_AMOUNT + "," +
+       WITHDRAWAL_FIELD_NAME_TERMINAL_ID + ")" +
+       " VALUES ($1, $2, $3, $4, $5, $6);"
 
-const PS_GET_WITHDRAWAL_BY_WOPID = "SELECT * FROM withdrawal WHERE wopid=$1"
+const PS_GET_UNCONFIRMED_WITHDRAWALS = "SELECT * FROM " + 
WITHDRAWAL_TABLE_NAME +
+       " WHERE " + WITHDRAWAL_FIELD_NAME_STATUS + " != '" + CONFIRMED + "'" +
+       " AND " + WITHDRAWAL_FIELD_NAME_STATUS + " != '" + ABORTED + "';"
+
+const PS_CONFIRM_WITHDRAWAL = "UPDATE " + WITHDRAWAL_TABLE_NAME + " SET (" +
+       WITHDRAWAL_FIELD_NAME_TRANSACTION_ID + "," +
+       WITHDRAWAL_FIELD_NAME_AMOUNT + "," +
+       WITHDRAWAL_FIELD_NAME_FEES + "," +
+       WITHDRAWAL_FIELD_NAME_COMPLETION_PROOF + ")" +
+       " = ($1, $2, $3, $4)" +
+       " WHERE " + WITHDRAWAL_FIELD_NAME_WOPID + "=$5"
+
+const PS_GET_WITHDRAWAL_BY_WOPID = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
+       " WHERE " + WITHDRAWAL_FIELD_NAME_WOPID + "=$1"
+
+const PS_GET_PROVIDER_BY_ID = "SELECT * FROM " + PROVIDER_TABLE_NAME +
+       " WHERE " + PROVIDER_FIELD_NAME_ID + "=$1"
+
+const PS_GET_TERMINAL_BY_ID = "SELECT * FROM " + TERMINAL_TABLE_NAME +
+       " WHERE " + TERMINAL_FIELD_NAME_ID + "=$1"
 
 // Postgres implementation of the C2ECDatabase
 type C2ECPostgres struct {
        C2ECDatabase
 
-       ConnPool *pgx.ConnPool
+       ctx  context.Context
+       pool *pgxpool.Pool
 }
 
-func (db *C2ECPostgres) RegisterWithdrawal(r *C2ECWithdrawRegistration) error {
+func NewC2ECPostgres(cfg C2ECDatabseConfig) (*C2ECPostgres, error) {
+
+       ctx := context.Background()
+       db := new(C2ECPostgres)
+
+       dbCfg := pgxpool.Config{
+               ConnConfig: &pgx.ConnConfig{
+                       Config: pgconn.Config{
+                               Host:     cfg.Host,
+                               Port:     uint16(cfg.Port),
+                               User:     cfg.Username,
+                               Password: cfg.Password,
+                       },
+               },
+       }
+
+       pool, err := pgxpool.NewWithConfig(ctx, &dbCfg)
+       if err != nil {
+               return nil, err
+       }
+
+       db.ctx = ctx
+       db.pool = pool
+
+       return db, nil
+}
+
+func (db *C2ECPostgres) RegisterWithdrawal(
+       wopid uint32,
+       resPubKey EddsaPublicKey,
+       amount Amount,
+       terminalId uint64,
+) error {
 
        ts := time.Now()
-       res, err := db.ConnPool.Query(
+       res, err := db.pool.Query(
+               db.ctx,
                PS_INSERT_WITHDRAWAL,
-               r.Wopid,
-               r.ReservePubKey,
-               ts,
-               r.Amount,
-               r.ProviderId,
+               wopid,
+               resPubKey,
+               SELECTED,
+               ts.Unix(),
+               amount,
+               terminalId,
        )
-       defer res.Close()
-
-       return err
+       if err != nil {
+               return err
+       }
+       res.Close()
+       return nil
 }
 
 func (db *C2ECPostgres) GetWithdrawalByWopid(wopid string) (*Withdrawal, 
error) {
 
-       return nil, errors.New("not yet implemented")
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_WITHDRAWAL_BY_WOPID,
+               wopid,
+       ); err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
+
+               withdrawals, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Withdrawal])
+               if err != nil {
+                       return nil, err
+               }
+
+               return withdrawals[0], nil
+       }
+}
+
+func (db *C2ECPostgres) ConfirmPayment(
+       providerTransactionId string,
+       amount Amount,
+       fees Amount,
+       completion_proof []byte,
+       confirmOrAbort WithdrawalOperationStatus,
+) error {
+
+       res, err := db.pool.Query(
+               db.ctx,
+               PS_CONFIRM_WITHDRAWAL,
+               providerTransactionId,
+               amount,
+               fees,
+               completion_proof,
+               confirmOrAbort,
+       )
+       if err != nil {
+               return err
+       }
+       res.Close()
+       return nil
 }
 
-func (db *C2ECPostgres) ConfirmPayment(c *C2ECPaymentNotification) error {
+// TODO this is probably not needed when using the LISTEN / NOTIFY feature
+func (db *C2ECPostgres) GetUnconfirmedWithdrawals(wopid string) 
([]*Withdrawal, error) {
+
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_WITHDRAWAL_BY_WOPID,
+               wopid,
+       ); err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
 
-       return errors.New("not yet implemented")
+               withdrawals, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Withdrawal])
+               if err != nil {
+                       return nil, err
+               }
+
+               return withdrawals, nil
+       }
 }
 
-func (db *C2ECPostgres) GetUnconfirmedWithdrawals() ([]*Withdrawal, error) {
+func (db *C2ECPostgres) AwaitWithdrawalStatusChange(
+       wopid string,
+       timeout time.Duration,
+       oldState WithdrawalOperationStatus,
+) (*Withdrawal, error) {
 
+       // TODO ... examples: 
https://github.com/jackc/pgxlisten/blob/master/pgxlisten_test.go
+       // -> Start a handler listening for the respective withdrawal
+       limitedTimeCtx, cancel := context.WithTimeout(db.ctx, timeout)
+       defer cancel()
+       conn, err := db.pool.Acquire(limitedTimeCtx)
+       if err != nil {
+               return nil, err
+       }
+       conn.Conn().PgConn()
+       conn.Conn().WaitForNotification(limitedTimeCtx)
        return nil, errors.New("not yet implemented")
 }
+
+func (db *C2ECPostgres) GetTerminalProviderById(id int) (*Provider, error) {
+
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_PROVIDER_BY_ID,
+               id,
+       ); err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
+
+               provider, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Provider])
+               if err != nil {
+                       return nil, err
+               }
+
+               return provider[0], nil
+       }
+}
+func (db *C2ECPostgres) GetTerminalById(id int) (*Terminal, error) {
+
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_TERMINAL_BY_ID,
+               id,
+       ); err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
+
+               terminal, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Terminal])
+               if err != nil {
+                       return nil, err
+               }
+
+               return terminal[0], nil
+       }
+}
diff --git a/c2ec/wire-gateway.go b/c2ec/wire-gateway.go
index 94fe58a..329c9b8 100644
--- a/c2ec/wire-gateway.go
+++ b/c2ec/wire-gateway.go
@@ -2,7 +2,6 @@ package main
 
 import (
        "bytes"
-       "c2ec/common"
        "log"
        http "net/http"
 )
@@ -26,17 +25,17 @@ type WireConfig struct {
 
 // https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferRequest
 type TransferRequest struct {
-       RequestUid      common.HashCode      `json:"request_uid"`
-       Amount          common.Amount        `json:"amount"`
-       ExchangeBaseUrl string               `json:"exchange_base_url"`
-       Wtid            common.ShortHashCode `json:"wtid"`
-       CreditAccount   string               `json:"credit_account"`
+       RequestUid      HashCode      `json:"request_uid"`
+       Amount          Amount        `json:"amount"`
+       ExchangeBaseUrl string        `json:"exchange_base_url"`
+       Wtid            ShortHashCode `json:"wtid"`
+       CreditAccount   string        `json:"credit_account"`
 }
 
 // https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferResponse
 type TransferResponse struct {
-       Timestamp common.Timestamp `json:"timestamp"`
-       RowId     int              `json:"row_id"`
+       Timestamp Timestamp `json:"timestamp"`
+       RowId     int       `json:"row_id"`
 }
 
 // https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingHistory
@@ -47,12 +46,12 @@ type IncomingHistory struct {
 
 // type RESERVE | 
https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingReserveTransaction
 type IncomingReserveTransaction struct {
-       Type         string                `json:"type"`
-       RowId        int                   `json:"row_id"`
-       Date         common.Timestamp      `json:"date"`
-       Amount       common.Amount         `json:"amount"`
-       DebitAccount string                `json:"debit_account"`
-       ReservePub   common.EddsaPublicKey `json:"reserve_pub"`
+       Type         string         `json:"type"`
+       RowId        int            `json:"row_id"`
+       Date         Timestamp      `json:"date"`
+       Amount       Amount         `json:"amount"`
+       DebitAccount string         `json:"debit_account"`
+       ReservePub   EddsaPublicKey `json:"reserve_pub"`
 }
 
 // https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingHistory
@@ -63,12 +62,12 @@ type OutgoingHistory struct {
 
 // 
https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingBankTransaction
 type OutgoingBankTransaction struct {
-       RowId           int                  `json:"row_id"`
-       Date            common.Timestamp     `json:"date"`
-       Amount          common.Amount        `json:"amount"`
-       CreditAccount   string               `json:"credit_account"`
-       Wtid            common.ShortHashCode `json:"wtid"`
-       ExchangeBaseUrl string               `json:"exchange_base_url"`
+       RowId           int           `json:"row_id"`
+       Date            Timestamp     `json:"date"`
+       Amount          Amount        `json:"amount"`
+       CreditAccount   string        `json:"credit_account"`
+       Wtid            ShortHashCode `json:"wtid"`
+       ExchangeBaseUrl string        `json:"exchange_base_url"`
 }
 
 func wireGatewayConfig(res http.ResponseWriter, req *http.Request) {
@@ -78,33 +77,33 @@ func wireGatewayConfig(res http.ResponseWriter, req 
*http.Request) {
                Version: "0:0:1",
        }
 
-       serializedCfg, err := 
common.NewJsonCodec[WireConfig]().EncodeToBytes(&cfg)
+       serializedCfg, err := NewJsonCodec[WireConfig]().EncodeToBytes(&cfg)
        if err != nil {
                log.Default().Printf("failed serializing config: %s", 
err.Error())
-               res.WriteHeader(common.HTTP_INTERNAL_SERVER_ERROR)
+               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
                return
        }
 
-       res.WriteHeader(common.HTTP_OK)
+       res.WriteHeader(HTTP_OK)
        res.Write(serializedCfg)
 }
 
 func transfer(res http.ResponseWriter, req *http.Request) {
 
-       res.WriteHeader(common.HTTP_OK)
+       res.WriteHeader(HTTP_OK)
        res.Write(bytes.NewBufferString("retrieved transfer request").Bytes())
 }
 
 func historyIncoming(res http.ResponseWriter, req *http.Request) {
 
-       res.WriteHeader(common.HTTP_OK)
+       res.WriteHeader(HTTP_OK)
        res.Write(bytes.NewBufferString("retrieved history incoming 
request").Bytes())
 }
 
 // This method is currently dead and implemented for API conformance
 func historyOutgoing(res http.ResponseWriter, req *http.Request) {
 
-       res.WriteHeader(common.HTTP_BAD_REQUEST)
+       res.WriteHeader(HTTP_BAD_REQUEST)
 }
 
 // ---------------------
@@ -113,18 +112,18 @@ func historyOutgoing(res http.ResponseWriter, req 
*http.Request) {
 
 // https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingRequest
 type AddIncomingRequest struct {
-       Amount       common.Amount         `json:"amount"`
-       ReservcePub  common.EddsaPublicKey `json:"reserve_pub"`
-       DebitAccount string                `json:"debit_account"`
+       Amount       Amount         `json:"amount"`
+       ReservcePub  EddsaPublicKey `json:"reserve_pub"`
+       DebitAccount string         `json:"debit_account"`
 }
 
 // 
https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingResponse
 type AddIncomingResponse struct {
-       Timestamp common.Timestamp `json:"timestamp"`
+       Timestamp Timestamp `json:"timestamp"`
 }
 
 // This method is currently dead and implemented for API conformance
 func adminAddIncoming(res http.ResponseWriter, req *http.Request) {
 
-       res.WriteHeader(common.HTTP_BAD_REQUEST)
+       res.WriteHeader(HTTP_BAD_REQUEST)
 }
diff --git a/docs/content/architecture/overview.tex 
b/docs/content/architecture/overview.tex
index edc5b24..549cea3 100644
--- a/docs/content/architecture/overview.tex
+++ b/docs/content/architecture/overview.tex
@@ -2,7 +2,7 @@
 
 \begin{figure}[h]
     \centering
-    
\includegraphics[width=0.7\textwidth]{pictures/diagrams/components_images.png}
+    
\includegraphics[width=0.7\textwidth]{pictures/diagrams/components_image.png}
     \caption{Involved components and devices}
     \label{fig-logo-components}
 \end{figure}
@@ -18,14 +18,14 @@ The component diagram shows the components involved by the 
withdrawal using the
     \label{fig-diagram-all-components}
 \end{figure}
 
-The \autoref{fig-diagram-all-components} shows a high level overview of the 
components involved and how they interact. The numbers in the diagrams are 
picked up by the description of the steps what is done between the different 
components:
+The \autoref{fig-diagram-all-components} shows a high level overview of the 
components involved and how they interact. In an initial step (before the 
process is effectively started as depicted), the customer or owner of the 
terminal selects the \textit{Exchange}, which shall be used for the withdrawal. 
The process is then started. The numbers in the diagrams are picked up by the 
description of the steps what is done between the different components:
 
 \begin{enumerate}
     \item Wallee Terminal requests to be notified when parameters are 
\textit{selected} by C2EC.
     \item The Wallet scans the QR code at the Terminal.
     \item The Wallet registers a reserve public key and the \textit{wopid}.
     \item The Bank-Integration API of C2EC notifies the Terminal, that the 
parameters were selected.
-    \item The Terminal accepts the credit card of the customer.
+    \item The POS initiates a payment to the account of the GNU Taler 
Exchange. For the payment the POS terminal requests a payment card and a PIN 
for authorizing the payment.
     \item The Terminal triggers the payment at the Wallee Backend.
     \item The Terminal receives the result of the payment.
     \begin{enumerate}
@@ -57,7 +57,7 @@ The Terminal initiates the withdrawal leveraging an 
application which works as f
 
 \begin{enumerate}
     \item At startup of the application, the terminal loads the C2EC 
configuration
-    \item When a user wishes to do a withdrawal, the owner of the terminal 
opens the application and initiates a new withdrawal.
+    \item When a user wishes to do a withdrawal, the owner of the terminal 
opens the application and initiates a new withdrawal. A withdrawal is basically 
a funds transfer to the IBAN account of the \textit{Exchange}.
     \begin{enumerate}
         \item Application creates a \textit{wopid}
         \item The application starts long polling at the C2EC and awaits the 
selection of the reserve parameters mapped to the \textit{wopid}. The 
parameters are sent by the Wallet to C2EC. 
@@ -68,7 +68,8 @@ The Terminal initiates the withdrawal leveraging an 
application which works as f
     \end{enumerate}
     \item The user now scans the QR Code using his Wallet.
     \item The application receives the notification of the C2EC, that the 
parameters for the withdrawal were selected.
-    \item The Terminal executes the payment (after user presented their credit 
card, using the Terminal Backend).
+    \item The Terminal executes the payment (after user presented their credit 
card, using the Terminal Backend). 
+    \item The terminal initiate the fund transfer to the \textit{Exchange}. 
The customer has to authorize the payment by presenting his payment card and 
authorizing the transaction with his PIN. The terminal processes the payment 
over the an available connector configured on the \textit{Wallee Backend}. 
Possible connectors are Master Card, VISA, TWINT, Maestro, Post Finance, and 
others \cite{wallee-available-connectors}.
     \begin{enumerate}
            \item It presents the result to the user.
            \item It tells the C2EC, that the payment was successful.
diff --git a/docs/content/implementation/c2ec.tex 
b/docs/content/implementation/c2ec.tex
new file mode 100644
index 0000000..bb80c00
--- /dev/null
+++ b/docs/content/implementation/c2ec.tex
@@ -0,0 +1,5 @@
+\section{C2EC}
+
+\subsection{Database}
+
+\subsection{Server}
diff --git a/docs/content/implementation/exchange.tex 
b/docs/content/implementation/exchange.tex
deleted file mode 100644
index bb05b29..0000000
--- a/docs/content/implementation/exchange.tex
+++ /dev/null
@@ -1 +0,0 @@
-\section{implementing C2EC docs}
\ No newline at end of file
diff --git a/docs/content/implementation/terminal.tex 
b/docs/content/implementation/terminal.tex
index 76a7a8e..d564bac 100644
--- a/docs/content/implementation/terminal.tex
+++ b/docs/content/implementation/terminal.tex
@@ -1 +1 @@
-\section{implementing terminal docs}
\ No newline at end of file
+\section{Wallee POS Terminal}
\ No newline at end of file
diff --git a/docs/content/implementation/wallee.tex 
b/docs/content/implementation/wallee.tex
deleted file mode 100644
index b97bea5..0000000
--- a/docs/content/implementation/wallee.tex
+++ /dev/null
@@ -1 +0,0 @@
-\section{implementing wallee docs}
\ No newline at end of file
diff --git a/docs/content/implementation/wallet.tex 
b/docs/content/implementation/wallet.tex
new file mode 100644
index 0000000..1c6e6d6
--- /dev/null
+++ b/docs/content/implementation/wallet.tex
@@ -0,0 +1 @@
+\section{Wallet}
\ No newline at end of file
diff --git a/docs/content/introduction/goal.tex 
b/docs/content/introduction/goal.tex
index eaae982..96d83d3 100644
--- a/docs/content/introduction/goal.tex
+++ b/docs/content/introduction/goal.tex
@@ -4,7 +4,7 @@ The goal of this thesis is to propose a framework for cashless 
withdrawals and i
 
 \subsection{C2EC}
 
-Therefore a new component, named \textbf{C2EC}, will be implemented as part of 
the Taler Exchange. C2EC will mediate between the Taler Exchange and the 
terminal provider. This includes checking that the transaction of the debitor 
reaches the account of the Exchange and therefore the digital currency can be 
withdrawn by the user, using its Wallet.
+Therefore a new component, named C2EC, will be implemented as part of the 
Taler Exchange. C2EC will mediate between the Taler Exchange and the terminal 
provider. This includes checking that the transaction of the debitor reaches 
the account of the Exchange and therefore the digital currency can be withdrawn 
by the user, using its Wallet.
 
-\subsection{Wallee}
-A new app for the payment terminal provider \textbf{Wallee} must be 
implemented which allows to start the withdrawal using providers facilities. 
The provider will guarantee through its backend, that the payment was 
successful. This puts the liability of the payment on the provider of the 
terminal.
+\subsection{Wallee POS Terminal}
+The Wallee payment terminal, also called Point of Sales (POS) terminal, 
interfaces with payment cards (Credit Cards, Debit Cards) to make electronic 
fund transfers, i.e. a fund transfer to a given GNU Taler Exchange. For our 
purpose, we will extend the functionality of the terminal to initiate the 
corresponding counter payment from the exchange to the GNU Taler wallet of the 
payee.
diff --git a/docs/pictures/diagrams/components_image.png 
b/docs/pictures/diagrams/components_image.png
new file mode 100644
index 0000000..a69d506
Binary files /dev/null and b/docs/pictures/diagrams/components_image.png differ
diff --git a/docs/pictures/diagrams/components_images.png 
b/docs/pictures/diagrams/components_images.png
deleted file mode 100644
index fd545d6..0000000
Binary files a/docs/pictures/diagrams/components_images.png and /dev/null differ
diff --git a/docs/project.bib b/docs/project.bib
index fa73952..eda9684 100644
--- a/docs/project.bib
+++ b/docs/project.bib
@@ -85,6 +85,13 @@
     howpublished = 
{\url{https://app-wallee.com/de-de/doc/api/web-service#refund-service}}
 }
 
+@misc{wallee-available-connectors,
+    author       = {Wallee},
+    title        = {Payment Connectors},
+    url          = {https://app-wallee.com/connectors},
+    howpublished = {\url{https://app-wallee.com/connectors}}
+}
+
 @misc{rfc8959,
     series =    {Request for Comments},
     number =    8959,
diff --git a/docs/thesis.pdf b/docs/thesis.pdf
index 162a819..a1563e5 100644
Binary files a/docs/thesis.pdf and b/docs/thesis.pdf differ
diff --git a/docs/thesis.tex b/docs/thesis.tex
index 3c43876..8d3665e 100644
--- a/docs/thesis.tex
+++ b/docs/thesis.tex
@@ -195,9 +195,9 @@
 \input{content/architecture/wallee}
 
 \chapter{Implementation}
-\input{content/implementation/exchange}
+\input{content/implementation/c2ec}
 \input{content/implementation/terminal}
-\input{content/implementation/wallee}
+\input{content/implementation/wallet}
 
 \chapter{Results}
 \input{content/results/discussion}
diff --git a/specs/components_images.odg b/specs/components_images.odg
index 14df174..c2798e2 100644
Binary files a/specs/components_images.odg and b/specs/components_images.odg 
differ

-- 
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]