commit 552d2fde8cf2f2c1db7f2cf84e78f7ef90202a35
parent fc93689cf80f9695e99dad88428a0ef14aa097d0
Author: Joel-Haeberli <haebu@rubigen.ch>
Date: Thu, 14 Mar 2024 16:22:19 +0100
code: add bank integration and wirewatch gateway api
Diffstat:
28 files changed, 836 insertions(+), 127 deletions(-)
diff --git a/docs/Makefile b/docs/Makefile
@@ -5,7 +5,7 @@ pdf:
clean-all:
latexmk -C
- rm -Rf *.glg *.glo *.gls *.ist *.lol *.bbl
+ rm -Rf *.glg *.glo *.gls *.ist *.lol *.bbl *.aux
clean:
latexmk -c
\ No newline at end of file
diff --git a/docs/content/appendix/meeting_notes.tex b/docs/content/appendix/meeting_notes.tex
@@ -98,16 +98,6 @@
\item How do we want to handle different currencies? How about currencies like Bitcoin? Currency is determined by the currency of the exchange.
\end{itemize}
-\textbf{Action points}
-\begin{itemize}
- \item
-\end{itemize}
-
-\textbf{Decisions}
-\begin{itemize}
- \item
-\end{itemize}
-
\subsection*{06.03.2024}
\textbf{Participants}
@@ -138,9 +128,25 @@
\item write SQL schema and generate UML using schema-spy instead of writing UML.
\end{itemize}
-\textbf{Decisions}
+\subsection*{13.03.2024}
+
+\textbf{Participants}
+
\begin{itemize}
- \item
+ \item Fehrensen Benjamin
+ \item Grothoff Christian
+ \item H\"aberli Joel
+\end{itemize}
+
+\textbf{Topics}
+\begin{itemize}
+ \item SQL Schema of nonce2ecash.
+\end{itemize}
+
+\textbf{Action points}
+\begin{itemize}
+ \item Add rst file to official docs Repository
+ \item Add proper versioning to the SQL script.
\end{itemize}
% TEMPLATE %
diff --git a/docs/content/architecture/exchange.tex b/docs/content/architecture/exchange.tex
diff --git a/docs/content/architecture/nonce2ecash.tex b/docs/content/architecture/nonce2ecash.tex
@@ -1,43 +1,51 @@
-\section*{nonce2ecash}
-
-\subsection*{REST API}
+\section*{The nonce2ecash RESTful API}
The API of the nonce2ecash component mirrors the flow from the creation of a nonce2ecash mapping to the creation of the reserve. Therefore three endpoints are implemented.
-An openapi specification of the API in yaml format can be found in \autoref*{appendix-api-spec}.
-
-\subsubsection*{Withdrawal Registration}
-\label{section-api-withdrawal-registration}
-
-With the \textit{withdrawal-registration} endpoint a nonce is registered at the nonce2ecash component of the Exchange. The \textit{N2CWithdrawalRequest} contains not only the nonce but also a reserve public key generated by the Wallet, the amount to withdraw and the provider type (e.g. \textit{WALLEE}). The provider type shall make the nonce2ecash component provider agnostic. Like this other providers can be added in the future.
+A specification of the API in yaml format can be found in \autoref*{appendix-api-spec}.
-\subsubsection*{Withdrawal Processing}
+\subsection{Authentication}
-With the \textit{withdrawal-processing} endpoint, the provider can notify the Exchange about the payment. Therefore the provider sends a \textit{N2CPaymentNotification} message to the API. The message contains the nonce to identify which withdrawal is processed, a provider specific transaction identifier which allows the nonce2ecash component to verify the payment at the providers backend and a flag indicating the success or failure of the payment.
+Terminals which authenticate against the nonce2ecash API must provide their respective access token. Therefore, they provide a \texttt{Authorization: Bearer \$ACCESS\_TOKEN} header, where \$ACCESS\_TOKEN is a secret authentication token configured by the exchange and must begin with the RFC 8959 prefix \textit{secret-token}.
-\subsubsection*{Withdrawal Status}
+\subsection{Configuration of nonce2ecash}
-With the \textit{withdrawal-status} endpoint, the status of a mapping can be retrieved. With a parameter called listenForStatus the consumer can initiate the a long-polling which will only end if the mapping comes to the requested state.
+\begin{itemize}
+ \item \textbf{Method:} GET
+ \item \textbf{Endpoint:} /config
+ \item \textbf{Description:} Return the protocol version and configuration information about the nonce2ecash API.
+ \item \textbf{Response:} HTTP status code 200 OK. The exchange responds with a \texttt{Nonce2ecashConfig} object.
+\end{itemize}
-\subsubsection*{Database}
+\subsection{Withdrawing using nonce2ecash}
-The database of the nonce2ecash component is small and only has one table and two enumerations, which are presented as separate tables themself.
+Withdrawals with a nonce2ecash are based on withdrawal operations which register a withdrawal identifier (nonce) at the nonce2ecash component. The provider must first create a unique identifier for the withdrawal operation (the \texttt{WITHDRAWAL\_ID}) to interact with the withdrawal operation and eventually withdraw using the wallet.
-\textbf{n2c\_status}
+\begin{itemize}
+ \item \textbf{Method:} POST
+ \item \textbf{Endpoint:} /withdrawal-operation
+ \item \textbf{Description:} Initiate the withdrawal operation, identified by the \texttt{WITHDRAWAL\_ID}.
+ \item \textbf{Request:} The request body contains a \texttt{WithdrawRegistration} object.
+ \item \textbf{Response:} HTTP status code 204 No content, 400 Bad request, or 500 Internal Server error.
+\end{itemize}
-The \textit{n2c\_status} table represents the enumeration of all possible states a mapping of a nonce to a reserve public key can be in.
+\begin{itemize}
+ \item \textbf{Method:} GET
+ \item \textbf{Endpoint:} /withdrawal-operation/\$WITHDRAWAL\_ID
+ \item \textbf{Description:} Query information about a withdrawal operation, identified by the \texttt{WITHDRAWAL\_ID}.
+ \item \textbf{Response:} HTTP status code 200 OK or 404 Not found.
+\end{itemize}
-\textbf{n2c\_provider\_type}
+\begin{itemize}
+ \item \textbf{Method:} POST
+ \item \textbf{Endpoint:} /withdrawal-operation/\$WITHDRAWAL\_ID
+ \item \textbf{Description:} Notifies nonce2ecash about an executed payment for a specific withdrawal.
+ \item \textbf{Request:} The request body contains a \texttt{PaymentNotification} object.
+ \item \textbf{Response:} HTTP status code 204 No content, 400 Bad request, 404 Not found, or 500 Internal Server error.
+\end{itemize}
-The \textit{n2c\_provider\_type} table represents the enumeration of all accepted providers for the nonce2ecash process. For this thesis, the only supported provider will be \textit{WALLEE}. In the future more providers might be implemented.
+\section*{The nonce2ecash database}
-\textbf{n2c\_nonce\_reservepubkey}
+The database of the nonce2ecash component must track two different aspects. The first is the mapping of a nonce to a reserve public key to enable withdrawals and the second aspect is the authentication of terminals allowing withdrawals owned by terminal providers like \textit{Wallee}.
-The \textit{n2c\_nonce\_reservepubkey} table represents the mapping of a nonce to a reserve public key. The table holds the nonce, the reserve public key, the \textit{n2c\_status}, the \textit{n2c\_provider\_type} and a timestamp which represents the time of registration of the withdrawal (The time when the \autoref*{section-api-withdrawal-registration}).
-\begin{figure}[h]
- \centering
- \includegraphics[width=0.7\textwidth]{pictures/diagrams/db_nonce2ecash_erd.png}
- \caption{Entity Relation Diagram of the nonce2ecash component}
- \label{fig-diagram-erd-nonce2ecash}
-\end{figure}
diff --git a/docs/content/architecture/overview.tex b/docs/content/architecture/overview.tex
@@ -1,3 +1,11 @@
+\section*{GNU Taler}
+% TODO
+General introduction to GNU Taler
+
+\section*{Wallee}
+% TODO
+General introduction to Wallee
+
\section*{Overview}
\subsection*{Components}
diff --git a/docs/content/context/context.tex b/docs/content/context/context.tex
@@ -0,0 +1,4 @@
+To better understand the architecture, specification and the implementation of the new components, this chapter contains the description of the necessary components of the Taler system and the Wallee system. This is however not a complete documentation or explanation but rather a compilation of the features needed to implement the respective components.
+
+\include{content/context/taler}
+\include{content/context/wallee}
+\ No newline at end of file
diff --git a/docs/content/context/taler.tex b/docs/content/context/taler.tex
@@ -0,0 +1,11 @@
+\section*{GNU Taler}
+
+GNU Taler is a payment system which allows paying for goods in an anonymous way while maintaining a trustworthy relationship between exchanges, merchants and customers. This goal is reached by implementing strong cryptographic primitives. Taler is wether a blockchain nor a currency. It is a payment system. It allows transferring money from A to B anonymously but still letting regulators control that no bad activities are going on.
+
+\subsection*{Wirewatch Gateway RESTful API}
+
+The wirewatch gateway helps communicating with the Exchanges core using convenient API. This includes retrieving information about transactions which were sent through the EBICS system. EBICS stands for \textit{Electronic Banking Internet Communication Standard} and represents an interface for interbank communication based on TCP/IP. Taler can retrieve incoming transaction through the EBICS interface. This will help nonce2ecash to capture the transaction of the Terminal Backend to the Exchange's account and therefore allow the withdrawal by the customer. Therefore the wirewatch gateway API is used in nonce2ecash.
+
+\subsection*{Bank Integration RESTful API}
+
+The Bank Integration API is used to withdraw digital currency. It supplies API to initiate a withdrawal and fetching status information about a withdrawal. When initiating a withdrawal a reserve is created at the exchange which contains digital currency signed by the exchange. Since these assets are linked to a reserve public key supplied by the Taler Wallet, they can be retrieved by the Wallet. The reserve will only be collectable, if the transaction was approved by the Exchange as valid and the money reached the Exchange's bank account.
diff --git a/docs/content/context/wallee.tex b/docs/content/context/wallee.tex
@@ -0,0 +1,5 @@
+\section*{Wallee}
+
+\subsection*{Wallee Terminal}
+
+\subsection*{Wallee Backend and API}
+\ No newline at end of file
diff --git a/docs/content/introduction/goal.tex b/docs/content/introduction/goal.tex
@@ -1,10 +1,10 @@
\section*{Goal}
-The goal of this thesis is to implement a process which allows withdrawing Taler using a credit card at a terminal.
+The goal of this thesis is to implement a process which allows withdrawing Taler using a credit card at a terminal of the terminal provider \textit{Wallee}.
-\subsection*{cashless2ecash}
+\subsection*{nonce2ecash}
-Therefore a new component, named \textbf{cashless2ecash (or maybe nonce2ecash??)}, will be implemented as part of the Taler Exchange. Cashless2ecash allows to check that the payment was accepted by the provider and therefore can guarantee, that the money of the withdrawing user will reach the bank account owned by the Taler Exchange.
+Therefore a new component, named \textbf{nonce2ecash}, will be implemented as part of the Taler Exchange. Nonce2ecash 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.
diff --git a/docs/content/introduction/introduction.tex b/docs/content/introduction/introduction.tex
@@ -1,51 +1,23 @@
\section*{Motivation}
-Ever tried withdrawing Taler? It is quite hard if your bank does not implement and run the facilities supplied by Taler ecosystem or you are not able to find people controlling the respective Exchange. The second approach is somewhat prone to human errors, but it works.
+Ever tried withdrawing Taler? It is quite hard if a bank you have access to, does not implement and run the facilities supplied by Taler or you are not able to find people controlling the respective Exchange. The second approach is somewhat strange and prone to human errors anyway, but it works.
So the problem currently is that it is not possible for users to get Taler if the above options are not feasible for them. This thesis proposes a way allowing users who own a credit card and a Taler Wallet, to buy Taler at a terminal supporting the withdrawal of Taler.
To make the withdrawal possible, various loose ends must be put together within the Taler ecosystem and the terminal provider. Also a new component called \textit{cashless2ecash} is implemented. The thesis focuses on the terminal provider called \textit{Wallee}. Therefore an application for the Wallee Terminal (PAX A50) for withdrawing Taler is implemented.
-With these components, a trustworthy relationship can be created, which makes it possible for the Exchange to issue coins to a user. Therefore the Exchange is not putting his trust on the money received but rather on the promise of a trusted third party (the terminal provider) to put the received money in a location, controlled by the Exchange eventually (e.g. a bank account owned by the Exchange).
+With these components, a trustworthy relationship can be created, which makes it possible for the Exchange to issue digital currency to a user. Therefore the Exchange is not putting his trust on the money received but rather on the promise of a trusted third party (the terminal provider) to put the received money in a location, controlled by the Exchange eventually (e.g. a bank account owned by the Exchange).
This enables a broader group of people to leverage Taler for their payments. Which eventually leads to wider adoption of the payment system Taler.
-\section*{Trust Relations}
-Withdrawing Taler coins establishes the need for a new trust model. In order to gather a deeper understanding of the difference between the current trust model and the new model they are described here.
-
-\subsection*{Direct Trust - Direct-Payment-Withdrawal}
-The \textit{direct trust} model is the model existing at the time. It allows the withdrawal of Taler using direct interaction between the deptor (withdrawing party) and the Taler Exchange. It allows the deptor to present credentials and values to the Exchange which it can claim and return the equivalent in coins. In case a deptor is unable to pay the amount he wants to buy coins from the Exchange, the Exchange will simply reject the request and tell the deptor that his balance is too low or the values are not legit. This is possible because he deptor gives the money directly to the Exchange. This means that the equivalent value of the withdrawn coins are immediately transferred to the Exchange and with it the trust anchor that the deptor has the money.
-
-The \textit{transaction} itself is the trust anchor. No trusted third party is needed.
-
-\subsection*{Indirect Trust - Eventual-Payment-Withdrawal}
-In the \textit{indirect trust} model is the model we must facilitate to be able to withdraw coins using a credit card. This comes from how payment systems are implemented and that transactions are not immediate but rather eventually executed. This means that if a payment is made using a credit card, the amount is not immediately transferred from the deptor to the seller. Instead the credit card provider (e.g. Mastercard or Visa) guarantees the fulfillment to the seller, by lending money (giving a credit) to the deptor. The credit card provider takes the risk to not be payed by the deptor and all its consequences (reminding the deptor to pay or going to court and filing a case against the deptor if he does not pay). The system therefore relies on \textit{trusting the credit card provider} guaranteeing the payment of the seller and doing the "dirty work" if a deptor won't pay. The Exchange shall therefore trust the credit card provider and its guarantees.
-
-The \textit{guarantee for the transaction} of the credit card provider is the trust anchor. The provider steps in as trusted third party.
-
-\subsection*{Why Wallee?}
-Wallee is not a credit card provider, but a payment system provider. So why should the Taler Exchange trust Wallee?
-% TODO: Why can we trust the terminals of Wallee?
-
-\subsection*{Chain of Trust}
-The above leads to a chain of trust that must be maintained in order for the system to be accountable and trustworthy. If one single link in the chain breaks our trust assumption, the chain of trust is broken and the system becomes untrustworthy and therefore not usable.
-
-\section*{GNU Taler}
-% TODO
-General introduction to GNU Taler
-
-\section*{Wallee}
-% TODO
-General introduction to Wallee
-
\section*{Perspectives}
During the initial analysis of the task, two main areas of work were discovered. One is the Taler Exchange and the other is the Application for the terminal. This led to different views on the system by two different players within it. To allow a more concise view on the system and to support the readers and implementer, two perspectives shall be kept in mind. They have different views on the process but need to interact with each other seamlessly.
\subsection*{Terminal Application}
-The perspective of the terminal application includes all processes within the application which interacts with the user and its credit card allowing the withdrawal of coins. The terminal application wants to conviently allow the withdrawal of coins and charge fees to cover costs and risks.
+The perspective of the terminal application includes all processes within the application which interacts with the user, his Taler Wallet and its credit card allowing the withdrawal of digital currency. The terminal application wants to conviently allow the withdrawal of digital currency and charge fees to cover its costs and risks.
-\subsection*{Taler Exchange (cashless2ecash)}
-The perspective of the Taler Exchange includes all processes within cashless2ecash component and the interaction with the terminal application, terminal backend and the wallet of the user. The Taler Exchange wants to allow withdrawal of coins only to users who pay the equivalent value to the Exchange and stay out of legal implications.
+\subsection*{Taler Exchange (nonce2ecash)}
+The perspective of the Taler Exchange includes all processes within nonce2ecash component and the interaction with the terminal application, terminal backend and the wallet of the user. The Taler Exchange wants to allow withdrawal of digital currency only to users who pay the equivalent value to the Exchange. The Exchange wants to stay out of any legal implications at all costs.
\section*{Fees}
Since buying Taler using a credit card leverages a third party payment system, new \textbf{fees} are introduced and must be taken care of. The fees to buy Taler are defined by the third party payment system and therefore do not lie in the control of the Taler system. The fees are retrieved by the terminal and added to the amount of money which is to be withdrawn by the user. Only after giving the confirmation to buy the specified amount of Taler with the specified amount of fees, the payment shall be processed.
diff --git a/docs/thesis.pdf b/docs/thesis.pdf
Binary files differ.
diff --git a/docs/thesis.tex b/docs/thesis.tex
@@ -177,6 +177,9 @@
\input{content/introduction/introduction}
\input{content/introduction/goal}
+\chapter{Context}
+\input{content/context/context}
+
\chapter{Architecture}
\input{content/architecture/overview}
\input{content/architecture/nonce2ecash}
diff --git a/nonce2ecash/go.mod b/nonce2ecash/go.mod
@@ -4,4 +4,15 @@ go 1.22.0
require gotest.tools/v3 v3.5.1
-require github.com/google/go-cmp v0.5.9 // indirect
+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/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
+ golang.org/x/crypto v0.17.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ gorm.io/gorm v1.25.7 // indirect
+)
diff --git a/nonce2ecash/go.sum b/nonce2ecash/go.sum
@@ -1,4 +1,29 @@
+github.com/davecgh/go-spew v1.1.0/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/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+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=
+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/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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/nonce2ecash/pkg/common/codec.go b/nonce2ecash/pkg/common/codec.go
@@ -7,9 +7,9 @@ import (
)
type Codec[T any] interface {
- httpApplicationContentHeader() string
- encode(*T) (io.Reader, error)
- decode(io.Reader) (*T, error)
+ HttpApplicationContentHeader() string
+ Encode(*T) (io.Reader, error)
+ Decode(io.Reader) (*T, error)
}
type JsonCodec[T any] struct {
diff --git a/nonce2ecash/pkg/common/http-util.go b/nonce2ecash/pkg/common/http-util.go
@@ -2,25 +2,46 @@ package common
import (
"errors"
+ "fmt"
"net/http"
"strings"
)
const HTTP_OK = 200
const HTTP_NO_CONTENT = 204
+const HTTP_BAD_REQUEST = 400
+const HTTP_UNAUTHORIZED = 401
const HTTP_NOT_FOUND = 404
const HTTP_CONFLICT = 409
const HTTP_INTERNAL_SERVER_ERROR = 500
// execute a GET request and parse body or retrieve error
-func HttpGetBodyOrError[T any](
+func HttpGet2[T any](
+ req string,
+ codec Codec[T],
+) (*T, int, error) {
+
+ return HttpGet(
+ req,
+ nil,
+ nil,
+ codec,
+ )
+}
+
+// execute a GET request and parse body or retrieve error
+// path- and query-parameters can be set to add query and path parameters
+func HttpGet[T any](
req string,
pathParams map[string]string,
queryParams map[string]string,
codec Codec[T],
) (*T, int, error) {
- res, err := http.Get(formatUrl(req, pathParams, queryParams))
+ url := formatUrl(req, pathParams, queryParams)
+ fmt.Println("GET:", url)
+
+ res, err := http.Get(url)
if err != nil {
return nil, -1, err
}
@@ -28,13 +49,32 @@ func HttpGetBodyOrError[T any](
if codec == nil {
return nil, res.StatusCode, err
} else {
- resBody, err := codec.decode(res.Body)
+ resBody, err := codec.Decode(res.Body)
return resBody, res.StatusCode, err
}
}
// execute a POST request and parse response or retrieve error
-func HttpPostOrError[T any, R any](
+func HttpPost2[T any, R any](
+ req string,
+ body *T,
+ requestCodec Codec[T],
+ responseCodec Codec[R],
+) (*R, int, error) {
+
+ return HttpPost(
+ req,
+ nil,
+ nil,
+ body,
+ requestCodec,
+ responseCodec,
+ )
+}
+
+// execute a POST request and parse response or retrieve error
+// path- and query-parameters can be set to add query and path parameters
+func HttpPost[T any, R any](
req string,
pathParams map[string]string,
queryParams map[string]string,
@@ -43,11 +83,14 @@ func HttpPostOrError[T any, R any](
responseCodec Codec[R],
) (*R, int, error) {
+ url := formatUrl(req, pathParams, queryParams)
+ fmt.Println("POST:", url)
+
var res *http.Response
if body == nil {
if requestCodec == nil {
res, err := http.Post(
- formatUrl(req, pathParams, queryParams),
+ url,
"",
nil,
)
@@ -65,14 +108,14 @@ func HttpPostOrError[T any, R any](
return nil, -1, errors.New("invalid arguments - body was present but no codec was defined")
} else {
- encodedBody, err := requestCodec.encode(body)
+ encodedBody, err := requestCodec.Encode(body)
if err != nil {
return nil, -1, err
}
res, err = http.Post(
- formatUrl(req, pathParams, queryParams),
- requestCodec.httpApplicationContentHeader(),
+ url,
+ requestCodec.HttpApplicationContentHeader(),
encodedBody,
)
@@ -86,7 +129,7 @@ func HttpPostOrError[T any, R any](
return nil, res.StatusCode, nil
}
- resBody, err := responseCodec.decode(res.Body)
+ resBody, err := responseCodec.Decode(res.Body)
if err != nil {
return nil, -1, err
}
@@ -109,7 +152,7 @@ func formatUrl(
// The function expects each parameter in the path to be prefixed
// using a ':'. The function handles url as follows:
//
-// /some/:param/tobereplaced -> ':param' will be replace with value.
+// /some/:param/tobereplaced -> ':param' will be replaced with value.
//
// For replacements, the pathParams map must be supplied. The map contains
// the name of the parameter with the value mapped to it.
@@ -119,14 +162,17 @@ func setUrlPath(
pathParams map[string]string,
) string {
- if pathParams == nil && len(pathParams) < 1 {
+ if pathParams == nil || len(pathParams) < 1 {
return req
}
var url = req
for k, v := range pathParams {
- url = strings.Replace(url, ":"+k, v, 1)
+ if !strings.HasPrefix(k, "/") {
+ // prevent scheme postfix replacements
+ url = strings.Replace(url, ":"+k, v, 1)
+ }
}
return url
}
@@ -136,7 +182,7 @@ func setUrlQuery(
queryParams map[string]string,
) string {
- if queryParams == nil && len(queryParams) < 1 {
+ if queryParams == nil || len(queryParams) < 1 {
return req
}
diff --git a/nonce2ecash/pkg/common/http-util_test.go b/nonce2ecash/pkg/common/http-util_test.go
@@ -0,0 +1,62 @@
+package common_test
+
+import (
+ "fmt"
+ "nonce2ecash/pkg/common"
+ "testing"
+)
+
+const URL_GET = "https://jsonplaceholder.typicode.com/todos/:id"
+const URL_POST = "https://jsonplaceholder.typicode.com/posts"
+
+type TestStruct struct {
+ UserId int `json:"userId"`
+ Id int `json:"id"`
+ Title string `json:"title"`
+ Completed bool `json:"completed"`
+}
+
+func TestGET(t *testing.T) {
+
+ res, status, err := common.HttpGet(
+ URL_GET,
+ map[string]string{
+ "id": "1",
+ },
+ map[string]string{},
+ common.NewJsonCodec[TestStruct](),
+ )
+
+ if err != nil {
+ t.Errorf("%s", err.Error())
+ t.FailNow()
+ }
+
+ fmt.Println("res:", res, ", status:", status)
+}
+
+func TestPOST(t *testing.T) {
+
+ res, status, err := common.HttpPost(
+ URL_POST,
+ map[string]string{
+ "id": "1",
+ },
+ map[string]string{},
+ &TestStruct{
+ UserId: 1,
+ Id: 1,
+ Title: "TEST",
+ Completed: false,
+ },
+ common.NewJsonCodec[TestStruct](),
+ common.NewJsonCodec[TestStruct](),
+ )
+
+ if err != nil {
+ t.Errorf("%s", err.Error())
+ t.FailNow()
+ }
+
+ fmt.Println("res:", res, ", status:", status)
+}
diff --git a/nonce2ecash/pkg/common/model.go b/nonce2ecash/pkg/common/model.go
@@ -6,11 +6,26 @@ type WithdrawalIdentifier string
// https://docs.taler.net/core/api-common.html#cryptographic-primitives
type EddsaPublicKey string
+// https://docs.taler.net/core/api-common.html#hash-codes
+type HashCode string
+
+// https://docs.taler.net/core/api-common.html#hash-codes
+type ShortHashCode string
+
+// https://docs.taler.net/core/api-common.html#timestamps
+type Timestamp struct {
+ Ts int `json:"t_s"`
+}
+
+// https://docs.taler.net/core/api-common.html#wadid
+type WadId [6]uint32
+
+// according to https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankWithdrawalOperationStatus
type WithdrawalOperationStatus string
const (
PENDING WithdrawalOperationStatus = "pending"
- SELECTED = "selected"
- ABORTED = "aborted"
- CONFIRMED = "confirmed"
+ SELECTED WithdrawalOperationStatus = "selected"
+ ABORTED WithdrawalOperationStatus = "aborted"
+ CONFIRMED WithdrawalOperationStatus = "confirmed"
)
diff --git a/nonce2ecash/pkg/db/db.go b/nonce2ecash/pkg/db/db.go
@@ -0,0 +1,66 @@
+package db
+
+import (
+ "context"
+ "strconv"
+ "strings"
+
+ "github.com/jackc/pgx/v5"
+)
+
+const POSTGRESQL_SCHEME = "postgres://"
+const NONCE2ECASH_DATABASE = "nonce2ecash"
+
+type DatabaseConfig interface {
+ ConnectionString() string
+}
+
+type PostgresNonce2ecashDatabaseConfig struct {
+ DatabaseConfig
+
+ host string
+ username string
+ password string
+ port int
+}
+
+func NewDbConf(
+ host string,
+ port int,
+ username string,
+ password string,
+) DatabaseConfig {
+
+ cfg := new(PostgresNonce2ecashDatabaseConfig)
+ cfg.host = host
+ cfg.port = port
+ cfg.username = username
+ cfg.password = password
+
+ return cfg
+}
+
+func NewDb(cfg DatabaseConfig) (*pgx.Conn, error) {
+
+ return pgx.Connect(
+ context.Background(),
+ cfg.ConnectionString(),
+ )
+}
+
+func (cfg *PostgresNonce2ecashDatabaseConfig) ConnectionString() 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/nonce2ecash/pkg/db/provider.go b/nonce2ecash/pkg/db/provider.go
@@ -0,0 +1,22 @@
+package db
+
+import "gorm.io/gorm"
+
+type TerminalProvider struct {
+ gorm.Model
+
+ ProviderTerminalID int64 `gorm:"primaryKey"`
+ Name string `gorm:"unique;not null"`
+ BackendBaseURL string `gorm:"not null"`
+ BackendCredentials string `gorm:"not null"`
+}
+
+type Terminal struct {
+ gorm.Model
+ TerminalID int64 `gorm:"primaryKey"`
+ AccessToken []byte `gorm:"type:bytea;not null;check:LENGTH(access_token)=32"`
+ Active bool `gorm:"not null;default:true"`
+ ProviderID int64 `gorm:"not null"`
+ Provider TerminalProvider `gorm:"foreignKey:ProviderID"`
+ Description string
+}
diff --git a/nonce2ecash/pkg/db/withdrawal.go b/nonce2ecash/pkg/db/withdrawal.go
@@ -0,0 +1,29 @@
+package db
+
+import (
+ "nonce2ecash/pkg/common"
+
+ "gorm.io/gorm"
+)
+
+type Withdrawal struct {
+ gorm.Model
+
+ WithdrawalId []byte `gorm:"type:bytea;primaryKey"`
+ ReservePubKey []byte `gorm:"type:bytea"`
+ RegistrationTs int64
+ Amount TalerAmountCurrency
+ Fees TalerAmountCurrency
+ WithdrawalStatus common.WithdrawalOperationStatus
+ TerminalId int64
+ ProviderTransactionId string
+ LastRetryTs int64
+ RetryCounter int32 `gorm:"default:0"`
+ CompletionProof []byte `gorm:"type:blob"`
+}
+
+type TalerAmountCurrency struct {
+ Val int64
+ Frac int32
+ Curr string `gorm:"size:12"`
+}
diff --git a/nonce2ecash/pkg/handler.go b/nonce2ecash/pkg/handler.go
@@ -13,7 +13,6 @@ const HTTP_METHOD_NOT_ALLOWED = 405
func config(writer http.ResponseWriter, req *http.Request) {
if isGet(req) {
-
writer.Write([]byte("{\n\"\":\"\",\n\"\":\"\"\n}"))
writer.WriteHeader(HTTP_OK)
}
@@ -39,6 +38,10 @@ func handleWithdrawalRegistration(writer http.ResponseWriter, req *http.Request)
}
+func handleWithdrawalStatus(writer http.ResponseWriter, req *http.Request) {
+
+}
+
func handlePaymentNotification(writer http.ResponseWriter, req *http.Request) {
}
@@ -55,5 +58,5 @@ func isPost(req *http.Request) bool {
func methodNotAllowed(writer http.ResponseWriter) {
- writer.WriteHeader(405)
+ writer.WriteHeader(HTTP_METHOD_NOT_ALLOWED)
}
diff --git a/nonce2ecash/pkg/taler-bank-integration/client.go b/nonce2ecash/pkg/taler-bank-integration/client.go
@@ -8,6 +8,7 @@ import (
const WITHDRAWAL_ID_PATH_PARAM_NAME = "withdrawal_id"
+const TALER_BANK_INTEGRATION_CONFIG_API = "/config"
const WITHDRAWAL_OPERATION_API = "/withdrawal-operation"
const WITHDRAWAL_OPERATION_BY_ID_API = WITHDRAWAL_OPERATION_API + "/:" + WITHDRAWAL_ID_PATH_PARAM_NAME
const WITHDRAWAL_OPERATION_ABORT_BY_ID_API = WITHDRAWAL_OPERATION_BY_ID_API + "/abort"
@@ -15,9 +16,10 @@ const WITHDRAWAL_OPERATION_ABORT_BY_ID_API = WITHDRAWAL_OPERATION_BY_ID_API + "/
type TalerBankIntegration interface {
init(string)
- withdrawalOperationStatus(common.WithdrawalIdentifier) (*BankWithdrawalOperationStatus, error)
- withdrawalOperationCreate(common.EddsaPublicKey, string) (*BankWithdrawalOperationPostResponse, error)
- withdrawalOperationAbort(common.WithdrawalIdentifier) error
+ BankIntegrationConfig() (*BankIntegrationConfig, error)
+ WithdrawalOperationStatus(common.WithdrawalIdentifier) (*BankWithdrawalOperationStatus, error)
+ WithdrawalOperationCreate(common.EddsaPublicKey, string) (*BankWithdrawalOperationPostResponse, error)
+ WithdrawalOperationAbort(common.WithdrawalIdentifier) error
}
type TalerBankIntegrationImpl struct {
@@ -26,6 +28,27 @@ type TalerBankIntegrationImpl struct {
exchangeBaseUrl string
}
+func (tbi *TalerBankIntegrationImpl) BankIntegrationConfig() (*BankIntegrationConfig, error) {
+
+ cfg, status, err := common.HttpGet(
+ tbi.exchangeBaseUrl+TALER_BANK_INTEGRATION_CONFIG_API,
+ nil,
+ nil,
+ common.NewJsonCodec[BankIntegrationConfig](),
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if status == common.HTTP_OK {
+
+ return cfg, nil
+ }
+
+ return nil, fmt.Errorf("HTTP %d - unexpected", status)
+}
+
// Initialize the taler bank integration implementation.
// The exchangeBaseUrl will be used as target by the impl.
func (tbi *TalerBankIntegrationImpl) init(exchangeBaseUrl string) {
@@ -34,11 +57,11 @@ func (tbi *TalerBankIntegrationImpl) init(exchangeBaseUrl string) {
}
// check status of withdrawal
-func (tbi *TalerBankIntegrationImpl) withdrawalOperationStatus(
+func (tbi *TalerBankIntegrationImpl) WithdrawalOperationStatus(
id common.WithdrawalIdentifier,
) (*BankWithdrawalOperationStatus, error) {
- withdrawalOperationStatus, status, err := common.HttpGetBodyOrError(
+ WithdrawalOperationStatus, status, err := common.HttpGet(
tbi.exchangeBaseUrl+WITHDRAWAL_OPERATION_BY_ID_API,
map[string]string{WITHDRAWAL_ID_PATH_PARAM_NAME: string(id)},
nil,
@@ -51,7 +74,7 @@ func (tbi *TalerBankIntegrationImpl) withdrawalOperationStatus(
if status == common.HTTP_OK {
- return withdrawalOperationStatus, nil
+ return WithdrawalOperationStatus, nil
}
if status == common.HTTP_NOT_FOUND {
@@ -63,13 +86,13 @@ func (tbi *TalerBankIntegrationImpl) withdrawalOperationStatus(
}
// send parameters for reserve to exchange core.
-func (tbi *TalerBankIntegrationImpl) withdrawalOperationCreate(
+func (tbi *TalerBankIntegrationImpl) WithdrawalOperationCreate(
id common.WithdrawalIdentifier,
reservePubKey common.EddsaPublicKey,
exchangPayToAddress string,
) (*BankWithdrawalOperationPostResponse, error) {
- bankWithdrawalOperationPostResponse, status, err := common.HttpPostOrError(
+ bankWithdrawalOperationPostResponse, status, err := common.HttpPost(
tbi.exchangeBaseUrl+WITHDRAWAL_OPERATION_BY_ID_API,
map[string]string{WITHDRAWAL_ID_PATH_PARAM_NAME: string(id)},
nil,
@@ -104,11 +127,11 @@ func (tbi *TalerBankIntegrationImpl) withdrawalOperationCreate(
}
// abort withdrawal
-func (tbi *TalerBankIntegrationImpl) withdrawalOperationAbort(
+func (tbi *TalerBankIntegrationImpl) WithdrawalOperationAbort(
id common.WithdrawalIdentifier,
) error {
- _, status, err := common.HttpPostOrError[any, any](
+ _, status, err := common.HttpPost[any, any](
tbi.exchangeBaseUrl+WITHDRAWAL_OPERATION_BY_ID_API,
map[string]string{WITHDRAWAL_ID_PATH_PARAM_NAME: string(id)},
nil,
diff --git a/nonce2ecash/pkg/taler-bank-integration/model.go b/nonce2ecash/pkg/taler-bank-integration/model.go
@@ -2,6 +2,25 @@ package talerbankintegration
import "nonce2ecash/pkg/common"
+// https://docs.taler.net/core/api-exchange.html#tsref-type-CurrencySpecification
+type CurrencySpecification struct {
+ Name string `json:"name"`
+ Currency string `json:"currency"`
+ NumFractionalInputDigits int `json:"num_fractional_input_digits"`
+ NumFractionalNormalDigits int `json:"num_fractional_normal_digits"`
+ NumFractionalTrailingZeroDigits int `json:"num_fractional_trailing_zero_digits"`
+ AltUnitNames string `json:"alt_unit_names"`
+}
+
+// https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankIntegrationConfig
+type BankIntegrationConfig struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+ Implementation string `json:"implementation"`
+ Currency string `json:"currency"`
+ CurrencySpecification CurrencySpecification `json:"currency_specification"`
+}
+
// https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankWithdrawalOperationPostRequest
type BankWithdrawalOperationPostRequest struct {
ReservePub string `json:"reserve_pub"`
diff --git a/nonce2ecash/pkg/taler-wirewatch-gateway/client.go b/nonce2ecash/pkg/taler-wirewatch-gateway/client.go
@@ -1 +1,226 @@
package talerwirewatchgateway
+
+import (
+ "errors"
+ "fmt"
+ "nonce2ecash/pkg/common"
+ "strconv"
+)
+
+const WIRE_GATEWAY_START_QUERY = "start"
+const WIRE_GATEWAY_DELTA_QUERY = "delta"
+const WIRE_GATEWAY_LONGPOLL_QUERY = "long_poll_ms"
+
+const WIRE_GATEWAY_API = ""
+const WIRE_GATEWAY_API_CONFIG = WIRE_GATEWAY_API + "/config"
+const WIRE_GATEWAY_TRANSFER_API = WIRE_GATEWAY_API + "/transfer"
+const WIRE_GATEWAY_HISTORY_INCOMING_API = WIRE_GATEWAY_API + "/history/incoming"
+const WIRE_GATEWAY_HISTORY_OUTGOING_API = WIRE_GATEWAY_API + "/history/outgoing"
+
+type TalerWirewatchGateway interface {
+ Init(string, string)
+
+ WirewatchGatewayConfig() (*WireConfig, error)
+ WirewatchGatewayTransfer(*TransferRequest) (*TransferResponse, error)
+ WirewatchGatewayHistoryIncoming(int, int, int) (*IncomingHistory, error)
+ WirewatchGatewayHistoryOutgoing(int, int, int) (*OutgoingHistory, error)
+}
+
+type TalerWirewatchGatewayImpl struct {
+ TalerWirewatchGateway
+
+ exchangeBaseUrl string
+ authToken string
+}
+
+func NewWirewatchGateway(exchangeBaseUrl string, authToken string) TalerWirewatchGateway {
+
+ twg := new(TalerWirewatchGatewayImpl)
+ twg.Init(exchangeBaseUrl, authToken)
+ return twg
+}
+
+func (twg *TalerWirewatchGatewayImpl) Init(exchangeBaseUrl string, authToken string) {
+
+ twg.exchangeBaseUrl = exchangeBaseUrl
+ twg.authToken = authToken
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#making-transactions
+func (twg *TalerWirewatchGatewayImpl) WirewatchGatewayConfig() (*WireConfig, error) {
+
+ res, status, err := common.HttpGet2(
+ WIRE_GATEWAY_API_CONFIG,
+ common.NewJsonCodec[WireConfig](),
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if status == common.HTTP_OK {
+
+ return res, nil
+ }
+
+ return nil, fmt.Errorf("HTTP %d - unexpected", status)
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#making-transactions
+func (twg *TalerWirewatchGatewayImpl) WirewatchGatewayTransfer(
+ transferRequest *TransferRequest,
+) (*TransferResponse, error) {
+
+ res, status, err := common.HttpPost2(
+ WIRE_GATEWAY_TRANSFER_API,
+ transferRequest,
+ common.NewJsonCodec[TransferRequest](),
+ common.NewJsonCodec[TransferResponse](),
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if status == common.HTTP_OK {
+
+ return res, nil
+ }
+
+ if status == common.HTTP_BAD_REQUEST {
+
+ return nil, errors.New("request malformed")
+ }
+
+ if status == common.HTTP_UNAUTHORIZED {
+
+ return nil, errors.New("authentication failed, likely the credentials are wrong")
+ }
+
+ if status == common.HTTP_NOT_FOUND {
+
+ return nil, errors.New("the endpoint is wrong or the user name is unknown")
+ }
+
+ if status == common.HTTP_CONFLICT {
+
+ return nil, errors.New("a transaction with the same request_uid but different transaction details has been submitted before")
+ }
+
+ return nil, fmt.Errorf("HTTP %d - unexpected", status)
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#querying-the-transaction-history
+func (twg *TalerWirewatchGatewayImpl) WirewatchGatewayHistoryIncoming(
+ optionalStartRow int,
+ deltaRows int,
+ optionalLongPollMsTimeout int,
+) (*IncomingHistory, error) {
+
+ res, status, err := common.HttpGet(
+ WIRE_GATEWAY_HISTORY_INCOMING_API,
+ nil,
+ buildHistoryQueryMap(optionalStartRow, deltaRows, optionalLongPollMsTimeout),
+ common.NewJsonCodec[IncomingHistory](),
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if status == common.HTTP_OK {
+
+ return res, nil
+ }
+
+ if status == common.HTTP_BAD_REQUEST {
+
+ return nil, errors.New("request malformed")
+ }
+
+ if status == common.HTTP_UNAUTHORIZED {
+
+ return nil, errors.New("authentication failed, likely the credentials are wrong")
+ }
+
+ if status == common.HTTP_NOT_FOUND {
+
+ return nil, errors.New("the endpoint is wrong or the user name is unknown")
+ }
+
+ if status == common.HTTP_CONFLICT {
+
+ return nil, errors.New("a transaction with the same request_uid but different transaction details has been submitted before")
+ }
+
+ return nil, fmt.Errorf("HTTP %d - unexpected", 0)
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#querying-the-transaction-history
+func (twg *TalerWirewatchGatewayImpl) WirewatchGatewayHistoryOutgoing(
+ optionalStartRow int,
+ deltaRows int,
+ optionalLongPollMsTimeout int,
+) (*OutgoingHistory, error) {
+
+ res, status, err := common.HttpGet(
+ WIRE_GATEWAY_HISTORY_OUTGOING_API,
+ nil,
+ buildHistoryQueryMap(optionalStartRow, deltaRows, optionalLongPollMsTimeout),
+ common.NewJsonCodec[OutgoingHistory](),
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if status == common.HTTP_OK {
+
+ return res, nil
+ }
+
+ if status == common.HTTP_BAD_REQUEST {
+
+ return nil, errors.New("request malformed")
+ }
+
+ if status == common.HTTP_UNAUTHORIZED {
+
+ return nil, errors.New("authentication failed, likely the credentials are wrong")
+ }
+
+ if status == common.HTTP_NOT_FOUND {
+
+ return nil, errors.New("the endpoint is wrong or the user name is unknown")
+ }
+
+ if status == common.HTTP_CONFLICT {
+
+ return nil, errors.New("a transaction with the same request_uid but different transaction details has been submitted before")
+ }
+
+ return nil, fmt.Errorf("HTTP %d - unexpected", 0)
+}
+
+func buildHistoryQueryMap(
+ optionalStartRow int,
+ deltaRows int,
+ optionalLongPollMsTimeout int,
+) map[string]string {
+
+ startStr := strconv.FormatInt(int64(optionalStartRow), 10)
+ deltaRowsStr := strconv.FormatInt(int64(deltaRows), 10)
+ longPollMsStr := strconv.FormatInt(int64(optionalLongPollMsTimeout), 10)
+
+ queryParams := map[string]string{
+ WIRE_GATEWAY_DELTA_QUERY: deltaRowsStr,
+ }
+ if optionalStartRow > 0 {
+ queryParams[WIRE_GATEWAY_START_QUERY] = startStr
+ }
+ if optionalLongPollMsTimeout > 0 {
+ queryParams[WIRE_GATEWAY_LONGPOLL_QUERY] = longPollMsStr
+ }
+
+ return queryParams
+}
diff --git a/nonce2ecash/pkg/taler-wirewatch-gateway/model.go b/nonce2ecash/pkg/taler-wirewatch-gateway/model.go
@@ -1 +1,93 @@
package talerwirewatchgateway
+
+import "nonce2ecash/pkg/common"
+
+// https://docs.taler.net/core/api-bank-wire.html#tsref-type-WireConfig
+type WireConfig struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+ Currency string `json:"currency"`
+ Implementation string `json:"implementation"`
+}
+
+// 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"`
+}
+
+// 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"`
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingBankTransaction
+// type IncomingBankTransaction = IncomingReserveTransaction | IncomingWadTransaction
+type IncomingBankTransaction struct {
+ IncomingReserveTransaction
+ IncomingWadTransaction
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingHistory
+type IncomingHistory struct {
+ IncomingTransactions []IncomingBankTransaction `json:"incoming_transactions"`
+ CreditAccount string `json:"credit_account"`
+}
+
+// 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 WAD | https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingWadTransaction
+type IncomingWadTransaction struct {
+ Type string `json:"type"`
+ RowId int `json:"row_id"`
+ Date common.Timestamp `json:"date"`
+ Amount common.Amount `json:"amount"`
+ CreditAccount string `json:"credit_account"`
+ DebitAccount string `json:"debit_account"`
+ OriginExchangeUrl string `json:"origin_exchange_url"`
+ WadId common.WadId `json:"wad_id"`
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingHistory
+type OutgoingHistory struct {
+ OutgoingBankTransaction []OutgoingBankTransaction `json:"outgoing_bank_transaction"`
+ DebitAccount string `json:"debit_account"`
+}
+
+// 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"`
+}
+
+// ---------------------
+// TESTING (ONLY ADMINS)
+// ---------------------
+
+// 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"`
+}
+
+// https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingResponse
+type AddIncomingResponse struct {
+ Timestamp common.Timestamp `json:"timestamp"`
+}
diff --git a/specs/api-nonce2ecash.rst b/specs/api-nonce2ecash.rst
@@ -1,7 +1,7 @@
..
This file is part of GNU TALER.
- Copyright (C) 2014-2023 Taler Systems SA
+ Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -16,15 +16,28 @@
@author Joel Häberli
-=====================
-Taler nonce2ecash API
-=====================
+===========================
+The nonce2ecash RESTful API
+===========================
-This chapter describe the APIs that third party providers need to integrate withdrawal through indirect
-payment channels like credit cards or ATM.
+.. note::
+
+ **This API is experimental and not yet implemented**
+
+This chapter describe the APIs that third party providers need to integrate to allow
+withdrawals through indirect payment channels like credit cards or ATM.
.. contents:: Table of Contents
+--------------
+Authentication
+--------------
+
+Terminals which authenticate against the nonce2ecash API must provide their respective
+access token. Therefore they provide a ``Authorization: Bearer $ACCESS_TOKEN`` header,
+where `$ACCESS_TOKEN`` is a secret authentication token configured by the exchange and
+must begin with the RFC 8959 prefix.
+
----------------------------
Configuration of nonce2ecash
----------------------------
@@ -53,7 +66,6 @@ Configuration of nonce2ecash
version: string;
}
-
-----------------------------
Withdrawing using nonce2ecash
-----------------------------
@@ -101,21 +113,21 @@ operation (the ``WITHDRAWAL_ID``) to interact with the withdrawal operation and
**Response:**
- .. ts:def:: CashlessWithdrawal
+ :http:statuscode:`200 Ok`:
+ The withdrawal was found and is returned in the response body as ``Withdrawal``.
+ :http:statuscode:`404 Not found`:
+ nonce2ecash does not have a withdrawal registered with the specified ``WITHDRAWAL_ID``.
- interface CashlessWithdrawal {
+ **Details**
- // Reserve public key generated by the wallet.
- // According to TALER_ReservePublicKeyP (https://docs.taler.net/core/api-common.html#cryptographic-primitives)
- reserve_pub_key: EddsaPublicKey;
- }
+ .. ts:def:: Withdrawal
- :http:statuscode:`204 No content`:
- The withdrawal was successfully registered.
- :http:statuscode:`404 Bad request`:
- No withdrawal with this ``WITHDRAWAL_ID`` exists.
- :http:statuscode:`500 Internal Server error`:
- The registration of the withdrawal failed due to server side issues.
+ interface Withdrawal {
+
+ // Reserve public key generated by the wallet.
+ // According to TALER_ReservePublicKeyP (https://docs.taler.net/core/api-common.html#cryptographic-primitives)
+ reserve_pub_key: EddsaPublicKey;
+ }
.. http:post:: /withdrawal-operation/$WITHDRAWAL_ID
diff --git a/specs/overview.plantuml b/specs/overview.plantuml
@@ -0,0 +1,40 @@
+@startuml
+
+actor Customer
+actor "Terminal Owner" as TerminalOwner
+
+package "EBICS System" {
+ [EBICS] as EBICS
+}
+
+package "Taler Exchange" {
+ [Bank Integration API] as BankIntegrationAPI
+ [Wirewatch Gateway] as WirewatchGateway
+ [nonce2ecash] as Nonce2Ecash
+ [Wallet] as Wallet
+ database "Exchange-DB" as ExDB
+}
+
+package "Wallee System" {
+ [Wallee Terminal] as WalleeTerminal
+ [Wallee Backend] as WalleeBackend
+}
+
+Customer --> Wallet
+TerminalOwner --> WalleeTerminal
+
+Nonce2Ecash --> ExDB
+WirewatchGateway --> ExDB
+BankIntegrationAPI --> ExDB
+Nonce2Ecash --> WirewatchGateway
+Nonce2Ecash --> BankIntegrationAPI
+Wallet --> WalleeTerminal
+Wallet --> Nonce2Ecash
+Wallet --> BankIntegrationAPI
+WirewatchGateway --> EBICS
+WalleeBackend --> EBICS
+WalleeTerminal --> WalleeBackend
+WalleeTerminal --> Nonce2Ecash
+Nonce2Ecash --> WalleeBackend
+
+@enduml