Designing a Payment System : Part 1
In this article, I am proposing a payment system design where I would explain the different terminologies, functional work flows, API designs and data models. This would give you a high level understanding of how a payment system works. Also you can find reference to different modules, API docs and engineering blogs which I have linked in this article for more in-dept understanding. This design is associated with payment done via cards. For other mode of payment, the design would be the same with minimal changes in API model and database model.
What is a payment system ?
A payment system is a system that is used to settle financial transactions through the transfer of monetary value which includes the institutions, instruments, people, rules, procedures, standards and technologies that makes its exchange possible. To make every transaction possible, the payment system has to be reliable, scalable and flexible.
Functional Requirements:
i) Pay-in-flow
ii) Pay-out-flow
Non-Functional Requirements:
i) Reliability and fault tolerance
ii) A reconciliation process between internal and external services.
High-level Design: Pay-in-flow
Terminology which we need to be aware off in comprehending the design and stakeholders who are part of this system are.
Payment service:
The payment service accepts payment events from users and coordinates the payment process. It only processes payments that pass this risk check which uses a third-party provider. This assesses for compliance with regulations and evidence of criminal activity.
Payment Executor:
The payment executor executes a single payment order via a Payment Service Provider(PSP). A payment event may contain several payment orders.
Card Schemes:
Card schemes are the organizations that process credit card operations. Some well known card schemes are Visa, MasterCard, American Express, Rupay,etc.
Ledger:
A ledger keeps a financial record of the payment transaction. Transaction such as debited or credited to an user is always tracked which helps in post-payment analysis.
Wallet:
A wallet keeps the account balance of the merchant which may also record how much a given user has paid in total.
Workflow : Pay-In-Flow
- When an user clicks on “place-order” button, a payment event is triggered which is sent to the payment service. The payment service then store this payment event in the database.
- A single payment event may contain several payment orders. Let’s say you have placed an order at blinkIt where you have added few vegetables, bakery items and some fruits. Here the order deals with different sellers. If the blinkIt splits the checkout into multiple payment orders, then the payment service calls the payment executor for each payment order.
- The payment executor stores the payment order in the database which later on calls an external PSP to process the payment.
- After the payment executor has processed the payment successfully, the payment service updates the wallet to record how much money a given seller has.
- The wallet server stores the updated balance information in the database. Once it is updated, the payment service calls the ledger to update it.
- The ledger service appends the new ledger information to the database.
APIs for Payment Service
We would use RESTful API design for implementing the payment service.
POST /v1/payments
This endpoint would execute a payment event which may contain multiple payment orders. It is similar to an API present in RazorPay docs.
https://razorpay.com/docs/api/payments/capture/.
The request parameter are listed below.
The payment_orders would have parameters listed below.
When the payment executor sends a payment request to a third party PSP, the payment_order_id is used by the PSP as the deduplication ID known as the idempotency key.
Note: “ The data type of amount field is string, not a double”
Reason: Different protocols, software and system may support different numeric precisions in serialization and deserialization. This difference might cause unintended rounding errors which could be extremely big or small depending upon the currency value.
GET /v1/payments/{:id}
This endpoint returns the execution status of a single payment order based on payment_order_id. It is similar to an api present in Razorpay docs.
https://razorpay.com/docs/api/payments/fetch-with-id/
The id used in the path variable for fetching the payment details is the unique global identifier id. As a response we can fetch the below parameter in form of a JSON.
To explore more endpoint APIs that would cover other edges case and scenario, you can checkout this API documentation from Razorpay.
https://razorpay.com/docs/api
Data model for payment service
We need to have two tables for payment service: payment_event and payment_order. When we select a storage solution for a payment system, performance is usually not the prime factor. It is stability of the storage system, supporting the monitoring and investigating tools and experienced DBAs. Usually a traditional relational database with ACID transaction is supported over NoSQL.
The checkout_id is the foreign key. A single checkout creates a payment event that may contain several payment orders.
When a third party PSP is called to deduct money from the buyer’s card, the money is not transferred to the seller directly. It first gets transferred to the merchant’s account i.e. the e-commerce merchant. This process is called pay-in. Once the product is delivered or delivery acknowledgment is issued then the seller initiates pay-out, where the money is transferred to the seller’s account from merchant’s account.
Pay-in flow deals with the buyer information not with seller’s bank information.
The payment_order_status is an enumerated type (enum) that keeps the execution status of the payment order. The execution status includes NOT_STARTED, EXECUTING,SUCCESS, FAILED. The update logic is
1. The initial status of the payment_order_status is NOT_STARTED.
2. When the payment service sends the payment order to the payment executor, the payment_order_status is EXECUTING.
3. The payment service updates the payment_order_status to SUCCESS or FAILED depending upon the response of the payment executor.
Once the payment_order_status is SUCCESS, the payment service calls the wallet service to update the seller balance and update the wallet_updated filed to TRUE.
Once it is done, the next step is for payment_service to call the ledger service to update the ledger database by updating the ledger_updated field to TRUE.
When all payment orders under the same checkout_id are processed successfully, the payment service updates the is_payment_done to TRUE in the payment_event table. A scheduled job usually runs at a fixed interval to monitor the status of the in-flight payment orders. It sends an alert when a payment order does not finish within a threshold so that an engineer can investigate it.
Double Entry Ledger System
A double entry ledger system is an important entity also known as a bookkeeping or double-entry accounting, where it records every payment transaction into two separate ledger accounts with the same amount. One the account is debited while the other is credited with the same amount.
The double entry system states that the sum of all transaction entries must be 0. It provides end-to-end traceability and ensures consistency throughout the payment cycle. Here is a blog that would give you more insight on how we can implement a double-entry ledger system.
https://developer.squareup.com/blog/books-an-immutable-double-entry-accounting-database-service/
Hosted payment page
Most companies do not prefer to store the card details internally because they have to deal with complex regulations such as Payment Card Industry Data Security Standard in US. To avoid handling card information, companies use hosted credit card pages provided by PSPs.
For websites, it is a widget or an iframe, while for mobile applications, it may be a pre-built page from the payment SDK.
The key point here is that the PSP provides a hosted payment page that captures the customer card information directly, rather than relying on the payment service.
Pay-out Flow
The components of pay-out flow are similar to the pay-in flow. The only difference is that instead of using PSP to move money from buyer’s card to the merchant’s bank account, the pay-out flow uses a third party pay-out provider to move the amount from merchant’s bank account to the seller’s bank account.
Till now, I have discussed the functional requirement at a very high-level. In the upcoming part, I would be diving deeping into the design where I would discuss on topics such as
1) PSP Integration
2) Reconciliation
3) Handling Payment Processing Delays
4) Communication among internal services
5) Handling failed payments
6) Exact-once delivery
7) Consistency
8) Security.
I hope this article would have given a small glimpse on how the payment system functions. I have gained this knowldeges from my industry experiences and from books. Incase, there is an edge case or scenarios which I have missed or it is done differently in the industry, do reach out to me.