[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-cashless2ecash] branch master updated: docs: add feedback,
gnunet <=