Payments processing using Golang: client and server
As part of a larger project of building banking infrastructure, I have begun work on the payments processing aspect of it. This post will detail progress thus far, as well as considerations for future development with a focus on scalability, security and regulations.
Adhering to a standard
There is currently a standard for a variety of financial instruments in the form of ISO20022. There is a wealth of documentation on this standard, as well as hundreds of XSD files describing each XML structure per type.
The approach I have taken is to tackle the implementation of this standard from the top down:
- Implement high-level payments messages types according to the messages list
- Handle the types correctly according to their purpose
- Implement the structure of the relevant XSD
- Do sanity checks on all messages for all types
The above is a fair amount of work, and so far only the first two are implemented. The plan is to implement all message types and then all relevant XSD types. This will be handled as it needs to, with standards coming after basic functionality has been handled.
Architecture
I’ve gone for the standard server-client architecture. The clients will create a connection to the server and send commands down which will be processed accordingly and a result returned. This is currently implemented over TCP, with the intention of moving this over to TLS.
All clients will connect to a port on the server and send messages back and forth over this connection. Distributed architecture has not been written in, but the code has been developed to take that into consideration.
An idea to be explored later will be the implementation of the blockchain as a transactions ledger, with all clients connecting to distributed ledgers, and the main server connecting to the ledger at a single source.
Implementing a standard
For the initial POC only the PAIN (Payments Initiation) message has been implemented. The implementation is also adapted slightly, and when stable will be moved into fuller adherence with the standards of ISO20022.
A client will send down a command on a single line with each data piece of the information broken by tilde (~). The implementation is as follows:
pain~painType~senderAccountNumber@senderBankNumber~receiverAccountNumber@receiverBankNumber~transactionAmount
There are several PAIN transaction types:
Payments initiation:
- 1 - CustomerCreditTransferInitiationV06
- 2 - CustomerPaymentStatusReportV06
- 7 - CustomerPaymentReversalV05
- 8 - CustomerDirectDebitInitiationV05
Payments mandates:
- 9 - MandateInitiationRequestV04
- 10 - MandateAmendmentRequestV04
- 11 - MandateCancellationRequestV04
- 12 - MandateAcceptanceReportV04
This is what a payment could look like:
pain~1~124356@~667437@00898~774646.32
In the above example, the payment is made from the local bank (the bank receiving the transaction from the client) so the senderBankAccount is left blank and defaults to the current bank. The PAIN type is a credit transfer and the amount is 774 646.32 (currencies are yet to be implemented). This is then parsed and processed.
For each type of transaction there are different fields. The PAIN transaction struct is as follows:
type PAINTrans struct {
painType int64
sender AccountHolder
receiver AccountHolder
amount float64
}
type AccountHolder struct {
accountNumber int64
bankNumber int64
}
The account holder struct will hold optional additional information later, such as addresses, full names, etc. This can be used for account verification and other functions.
Client and Server
The client and server both run from the same code base. These are triggered using the mode
flag.
go run *.go --mode=client
go run *.go --mode=server
The client is then used as follows:
go run *.go --mode=client
Go Banking Client
Welcome
pain~1~567@~67328@99887~67362381.32
Message from server: Message received.
Corresponding server response:
go run *.go --mode=server
Listening on localhost:3333
### pain~1~567@~67328@99887~67362381.32 ####
Validating PAIN ...
Process transaction {1 {567 0} {67328 99887} 6.736238132e+07}
The above output includes various debugging, with the most important line being the last. This is the resulting struct object containing all of the PAIN transaction information after being validated and parsed.
The server uses go channels to enable asynchronous processes. The message sent back to the client will also include some useful information, perhaps a transaction ID and/or the status of the transaction.
A long to do list
There is still loads of work to be done, but the project is at a point where some progress can be shared. A few of the following steps to be implemented in the project are as follows:
- Store the transactions in a database
- An entry into the transactions table
- Amendments on the sender and recipient accounts
- Fees applied to the sender accounts (if fees are applied)
- These fees will then be added to the bank’s holding account
- Secure TCP connection with TLS
- Further secure connection with authorization
- Finalise the standard to bring in line with ISO20022
- Currency support
Once all of this is done, we can get into the exciting territory of loans and investments, and the regulations surrounding that.
Conclusion
The above leaves us with a functional implementation of one type of payment loosely in accordance with the ISO20022 standards. We have a working client and server over TCP sending and receiving commands according to a local standard.
The code for the project is available on Github.
Cover image by Tom Bland