As one of the founding engineers at inai, a payment orchestration platform that integrates with hundreds of payment methods and gateways worldwide, I’ve had the opportunity to learn a lot about the complexities of global payments. Before inai, I worked in the customer support SaaS software space, so a lot of the payment gateway terminology and nuances were new to me. Also made me remember the wise words of an Angry Hacker
“The Internet from every angle has always been a house of cards held together with defective duct tape. It’s a miracle that anything works at all. Those who understand a lot of the technology involved generally hate it, but at the same time are astounded that for end users, things seem to usually work rather well. “
Note to self : Get better at using dall-e to generate cover art.
Turtles all the way down: What happens when you swipe your card
When you swipe your credit or debit card, it initiates a complex sequence involving the cardholder, merchant, acquirer (merchant’s bank), processor, card network, and issuing bank. The process starts with the card swipe, information flows through these entities for authorization, and if approved, a series of authorization messages are relayed, culminating in the completion of the transaction.
It sounds and looks beautiful. When it works.
Payments are not final
When I was a payments noob, I thought that receiving a 200 response from Stripe meant that a merchant was going to get paid. I learned that’s not true, while close to the truth a 200 response from the payment gateway ONLY indicates that the payment gateways has received the request to kick start a complex payment process.
As the UML diagram above shows, there are many steps that can fail after a 200 response from the payment gateway. For example:
- The acquirer may decline the transaction.
- The issuer may decline the transaction.
- The processor may experience a technical outage.
- The merchant’s bank account may be insufficiently funded.
- The customer may file a chargeback.
In any of these cases, the merchant will not receive payment. There are tons of such examples in Patrick McKenzie’s blog
This is why it is important for merchants to understand the difference between HTTP status codes and transaction statuses. An HTTP status code of 200 simply means that the payment gateway has received the request and processed it successfully. It does not mean that the transaction has been approved or that the merchant will receive payment.
This is also why it is important for merchants to implement webhook handling. Webhooks allow merchants to receive real-time notifications about the status of their transactions. This way, merchants can be alerted to any problems immediately and take corrective action if necessary.
HTTP status != Transaction Status
An HTTP status code is a three-digit number that indicates the result of an HTTP request. The most common HTTP status codes are:
- 200 OK: The request was successful.
- 400 Bad Request: The request was malformed or incomplete.
- 401 Unauthorized: The client is not authorized to access the resource.
- 404 Not Found: The resource does not exist.
- 500 Internal Server Error: An unexpected error occurred on the server.
A transaction status is a different concept. It indicates the state of a payment transaction.
Here is an example where Stripe says 200 OK but the transaction status JSON shows that the transaction has failed:
{
"id": "ch_1234567890",
"object": "charge",
"amount": 1000,
"currency": "usd",
"status": "failed",
"failure_code": "card_declined",
"failure_message": "The card was declined by the issuing bank."
}
This response indicates that Stripe received and processed the payment request successfully, but the transaction was ultimately declined by the issuing bank. This could happen for a variety of reasons, such as insufficient funds in the customer’s account or a suspected fraudulent transaction.
Even though the transaction failed, Stripe will still return a 200 OK HTTP status code. This is because the HTTP status code is only indicative of the success of the API call, not the outcome of the transaction itself.
To get the true status of a transaction, merchants should always inspect the transaction status JSON. If the status is failed, then the merchant should take appropriate action, such as notifying the customer of the declined transaction.
Webhook handling is as important as the API call
Webhook handling is an important part of any payments system. It allows merchants to receive real-time notifications about the status of their transactions. This way, merchants can be alerted to any problems immediately and take corrective action if necessary, so if you are evaluating a payment gateway integration, don’t just evaluate the APIs, also make headroom for the callback and how you can eventually identify the status of a payment.
For example, a merchant could use webhooks to:
- Be alerted to declined transactions so that they can contact the customer and resolve the issue.
- Be alerted to refunds so that they can update their inventory and customer records.
- Be alerted to chargebacks so that they can dispute them with the payment processor.
Don’t rely on webhooks alone - Reconcile often.
Design for webhooks, but don’t rely on them alone. The safer approach would be to have a mechanism to poll the payment processor to get the latest transction status. Webhooks can fail and you are banking on webhooks to have a retry mechanism which may not be true for all payment gateways. A fail safe approach would be to have your own polling mechanims ensuring you don’t poll too often and get blocked by the gateway.
Components in a payment gateway integration
There were a few components that I could see were repeated in all payment gateway integrations
PG Client - To manage synchronous communication with the payment gateway
PG Webhook Handler - To manage webhooks recieved from the payment gateway
PG Reconciler - A reconciler to poll the payment gateway
My key takeaways from working with different payment gateways are:
It helps to think of payments as something that happen async and designing systems accordingly. I think there’s a small but crucial mind shift when you start thinking of an operation that would eventually signal completion rather than something that would immediately tell you if the payment is complete.
1. Asynchronous payments are the norm.
2. Webhooks are equally important as API calls.
3. Don’t rely on webhooks alone, reconcile often.
4. Understand different types of transaction codes.
5. Imagine the whole payments network as if it were discworld
After completing tons of payment integrations, I couldn’t help imagining if Terry Prachett would compare a payments system to great a’tuin and 4 elephants.
Why imagine when you can ask ChatGPT to describe it in Terry Pratchett style ?
“Think of the payment system as the Discworld itself, a flat world perched on the backs of four colossal elephants, which, in turn, stand atop the shell of a giant turtle. It’s a realm where coins spin like the stars and payments flow like magical rivers. But beware, for in this fantastical place, financial transactions are a cosmic ballet, where money appears and vanishes, and commerce is a surreal adventure amidst guilds, wizards, and curious creatures. It’s a realm where the improbable is the norm, much like the Discworld itself”