Processing payments seems incredibly easy today — just drop in Stripe or Adyen and you are done. But things get interesting when your transaction volume scales and those Payment Service Provider (PSP) fees start taking a massive bite out of your margins. Eventually, you realize there is a vast ecosystem of smaller, regional PSPs that charge less, cover local payment methods the major players ignore, and can significantly boost your conversion rates.
In this article, I will explain how modern Payment Gateways (PG) actually work, specifically when you outgrow a single provider and need to build your own system to route payments across multiple PSPs.
The Illusion of Simplicity
Looking at the Stripe API, integrating a new provider seems as easy as it gets: a simple HTTP request with a JSON payload and a 200 OK in response. Reality is rarely so kind.
While many PSPs try to offer simple interfaces, customer demands and local niches force them to require highly specific additional data. Mix in strict country regulations and regional fraud protection, and in the blink of an eye, you are dealing with a massive, messy protocol with outdated documentation. Some PSPs even invent their own authorization methods or enforce government-approved encryption standards — usually just slightly modified versions of existing algorithms, but enough to make your head hurt.
To a frontend engineer, a payment is just a JSON payload and a success screen. The truly interesting and complicated parts happen on the backend. After that initial request, we don’t just call a PSP and wait for a callback. That is never the end of it. The business always wants more: intelligent routing, cascading fallbacks, fee calculations, currency conversions, and — holly electron forbid — ledger balances with full financial reconciliation. Add legacy XML, SOAP, or plain TCP socket protocols to the mix, and you have a real engineering challenge.
So, what actually happens between the user clicking “Submit” and the payment finishing? It generally follows this lifecycle:
Security & Fraud Detection: First and foremost, the user and their credentials are screened for fraudulent activity.
Routing & Pre-processing: The Payment Gateway selects the optimal payment method and PSP (routing based on cost, region, and reliability). It verifies all required parameters are present and calculates any necessary fees (e.g., subtracting your internal fee from a withdrawal amount before sending it out).
The Request: The PG sends the payment payload to the chosen PSP.
PSP Processing: The external PSP does its own processing (their own anti-fraud, fund reservation, and settlement) and eventually sends a callback with a transaction status: processing, success, or failure.
Post-processing & Conversion: The PSP might process or settle the transaction in a different currency, meaning you must handle currency conversion on your side. If it’s a deposit, you calculate the final fee and settle a slightly smaller amount into the merchant’s internal account.
Cascading (On Failure): If the transaction fails, the flow isn’t necessarily over. Through cascading, the gateway can seamlessly choose a backup PSP and loop back to the request stage to save the transaction.
There are countless nuances at every step, and the edge cases can go on forever. But for the user, this all happens in 10 to 15 seconds (and we fight constantly to reduce that time). For the backend, it is a very long and complex story.
Meet The Crew
From an architectural standpoint, here are the core components you need to build to manage this complexity.
The Payment Gateway (The Core Engine and Internal Protocol): This is the orchestrator, the accountant, and the primary validator. This is the API your merchants or frontends actually talk to. Most of the magic happens here: validating user input, routing to the most cost-effective and reliable PSP, deducting fees, handling local government fiscalization, and executing cascading logic on failures.
The Adaptor (The Translator and External Protocol): Almost every external PSP uses its own protocol — JSON, XML, SOAP, etc. A highly viable architectural strategy is to use the Adaptor Pattern. You build microservices whose sole job is to translate these weird external protocols into your Gateway’s standard internal protocol. I can’t help but mention the old engineering joke: “We have 15 different standards, we need to unite them all. Great, now we have 16 different standards.” But in this case, it is strictly necessary. It separates the workload: one part of your engineering team can grind through external integrations, while the core team focuses on improving the main engine.
The Payment Service Provider (PSP): To tell you the truth, a PSP is basically just another Gateway developed by a different team. They have their own internal routing, methods, and core engines. The only difference is that you communicate with them through their External Protocol. To your internal Gateway, a PSP is simply a provider of one or more payment methods. Even if a PSP is a direct integration with a tier-one bank via PCI-DSS, architecturally, it’s just another method in your pool.
There is a hidden beauty to defining your own strict, standardized external protocol: once your transaction volume is high enough, you can flip the script. Instead of your team building an Adaptor for a new PSP, you can hand the PSP your API documentation and force them to integrate with your protocol.
But beware this trap. While it drastically reduces your initial development costs and workload, it shifts the burden directly onto your support and operations teams. It looks great on paper, but integrations require maintenance. If your traffic to that specific PSP drops and your business becomes less relevant to their bottom line, they will stop maintaining the integration. When their code breaks, it becomes a massive headache for your support team to get them to fix it.
Next time i plan to talk about the lifecycle of a transaction in detail. When it is created, why we do neet such entity and for what purpose we can use it. Hope to see you!


Top comments (0)