By Mark Arts, Java Developer, and Otto Moerbeek, C Developer, Adyen
Today, point of sale (POS) payments still account for most global volume. Yet most point of sale systems are outdated and cumbersome, requiring significant setup costs and an inability to roll out new features quickly. As POS developers at Adyen, we recently adopted a new API to simplify the POS payments setup.
Most POS setups include a cash register, controlled by store staff, a payment terminal, where the shopper enters their card, and a serial connection between the two. A library is embedded on the cash register facilitating communication between the cash register and the payment terminal. These libraries are typically created and maintained by the company that facilitates the terminals (such as Adyen).
Using libraries creates a number of challenges:
A tight integration between the cash register and the library means a significant amount of setup and development work is required, because the library will be part of the cash register software.
- The cash register software — which is third party — is often updated as infrequently as once a year, meaning retailers are not able to immediately benefit from the latest library updates.
- Cash registers differ significantly between vendors and platforms, creating a large maintenance burden on the development of the library for Adyen.
Furthermore, many larger retailers prefer centrally-hosted solutions for their cash register software. This means the software needs to be configured to initiate a transaction on a payment terminal in the store, by routing requests from the data center into the store network. To do this, merchants need to use port forwarding to manage payments across multiple locations, a fixed IP for each terminal, or possibly a VPN setup for security. All these possibilities involve a complex network setup that drains operational resources.
Solving the library challenge with the Nexo protocol
Ideally, we needed a solution that would be independent of any specific platform, able to be used for serial connections, local network, and internet transports, and support a message format with advanced features such as asynchronous notifications.
To meet these criteria, we removed our need for libraries and created the Terminal API, adopting the Nexo protocol — a card payment standard that facilitates communication between the cash register and terminal.
Nexo’s basic interaction model is request/response JSON messaging. This means that making a payment with the Terminal API is a simple request-response, and all informational events, such as notifying where the terminal is in the payment process, are communicated via JSON webhooks that are optionally implemented.
Using this approach is advantageous because:
- Supporting new programming languages is simpler, as the library required all potential events to be implemented as a callback and passed as part of the initial payment request.
- Maintaining a JSON messaging format, rather than custom libraries, callbacks, and SDKs, makes it far easier for merchants to roll out and update the software.
- Internally, this had the added benefit of us not needing to support multiple programming languages for the API.
Solving network setup complexity by routing through the cloud
Using the Terminal API over the store network was a great first step. However, it did not solve the challenge of initiating payments from a centralized place such as a data center.
To simplify the setup investment and remove the cost of all this complexity, we also adapted our Terminal API for the cloud. The in-store architecture relied on the merchant’s cash register and backend to communicate to the terminal, as below:
In the cloud version of the API, we added the ability for the merchant to initiate a terminal payment directly with Adyen’s backend.
One advantage of serial connections is that they provide bidirectional communication, so both cash register and payment terminals can initiate communication and exchange data related to the status of the transaction. With our Terminal API over the network, transactions are https request-response. The cash register initiates a payment request by sending an https request to the terminal.
However, on the internet, having a communication channel where both parties can initiate communication is cumbersome, as the NATed terminals cannot be reached without opening the firewall and setting up port forwarding. We needed a solution to easily enable bidirectional communication.
We found this solution in WebSockets. This technology is used by a number of platforms for push notifications, such as in newsfeeds, and we leveraged it for communication between a terminal and the Adyen backend.
To enable bidirectional communication, we create a single https request from the payment terminal, and added headers to request an upgrade the connection to a WebSocket, as displayed below. After that, a bidirectional communication channel is established between our backend and the payment terminal.
A standard flow is as follows:
- As the cash register initiates a transaction, it sends an https request to the Adyen backend.
- The Adyen backend looks up which WebSocket the terminal is using and routes the request to the terminal over it (more on this below under load balancing).
- The terminal delivers its response to the Adyen backend over the WebSocket and the backend subsequently delivers it as a https response to the cash register.
Load balancing and redundancy
Redundancy is a key consideration in our system architecture. During application updates, or when carrying out maintenance, transactions cannot be affected. Our payment acceptance layer is made up of multiple servers over multiple data centers around the world. This helps reduce latency and ensure redundancy. (Note: you can read more about our approach to redundancy and database setup here: Updating a 50 terabyte PostgreSQL database).
This infrastructure ensures redundancy and the possibility to balance loads if we need to carry out maintenance. However, it does raise a new challenge — when a terminal opens a connection with Server A, and a cash register with server B, what happens? We configured our setup so that if a terminal connects to Server A, a message is triggered from that server to other servers that says “I now have a connection with this terminal.” If a cash register then connects to Server B, Server B can look up which server owns the WebSocket connection, and route the message via that server.
If we need to carry out application updates or maintenance on one server, it sends a message to the terminal to reconnect with another server. Once all connections are closed we can begin.
Our Terminal API simplifies rollout and ensures merchants are able to stay abreast of the latest software updates. However, there are more innovative ways in which it may be used. For example, since in-store payments can be initiated remotely, merchants would be able to create an experience where a shopper initiates an order in-app, walks into a store, scans a QR code with their phone to initiate the payment on the terminal, and picks up their item. These kinds of possibilities make it very exciting for us to see how merchants use this technology.