System Design Interview - Designing Payment Systems; Follow-up Questions and Probable Issues

Written by kliukovkin | Published 2022/11/09
Tech Story Tags: system-design-interview | system-design | design-system-examples | design-systems | payment-processing | stripe | designing-payment-system | digital-payments

TLDRA system design interview looks like this: 1. Functional requirements, 2. Non-functional requirements, APIs, data structures, high-level design and data structures. We need to handle any failure we may face while processing the payment. Our system should be reliable and fault-tolerance. How to handle sensitive data, how to handle credit card information? How to make the system reliable. How to host payment pages provided by payment service providers like Stripe, Braintree, etc. If we want to collect $100M and there will be 10 charities so each user may decide where he/she wants to donate the money.via the TL;DR App

Initial

We will have a charity event that will last for 3 days(let’s say from Friday to Sunday). We want to collect $100M and there will be 10 charities so each user of our system may decide where he/she wants to donate the money. We may assume that the front-end part is ready and also we have an agreement with some 3d party payment system so there is no need to handle money transfers from one account to another. Also, we need to collect all money in a single bank account and after the event will be finished we need somehow to spread the collected money among all 10 charities. The last thing to mention is that we need to handle any failure we may face while processing the payment.

Functional Requirements

  • Process transactions using 3d party service (payment service providers like Stripe, Braintree, etc)
  • Keep track of which donations belong to a particular charity (to be able to spread all donated money among charities)

Non-Functional requirements

Since we are going to work with payments our system should be highly consistent, we need to handle stale data.

Also since will use some 3d party service that will handle our transactions we need to provide some reconciliation process (a process that will ensure that our stored data is consistent). Our system should be reliable and fault-tolerance.

Usually, a plan for a system design interview looks like this:

  1. Functional requirements
  2. Non-functional requirements
  3. APIs
  4. Data structures
  5. High-level design
  6. Design deep dive
  7. Wrap up

But in this article, we will focus on the high-level design to discuss probable issues with it and follow-up questions we might face during the interview.

High-level design

  1. The user submits the form on the UI(form contains credit card information, charity ID, and amount of money the user wants to donate to this charity
  2. The web server calls PSP to proceed payment
  3. PSP responded with an acknowledgment that payment proceeded successfully
  4. The web server saves data to the DB

Issues with that design:

  • the user clicked on submit button twice, what will happen in that case? Does the system proceed with 2 different payments?
  • working with sensitive data, how to handle credit card information?
  • how to make the system reliable and fault-tolerance?

Click on “submit” button multiple times

We can handle this issue in several ways:

  • we may have some javascript restrictions, but it won’t help if the user disables javascript in the browser. This is also not so reliable since users may try to change your javascript code(rather than you providing some obfuscation to your code to make this action almost impossible)
  • provide idempotent API with two-step processing.

Let’s go ahead with the second approach. First, we provide a page with a dropdown of 10 charities, the amount user wants to donate, and the button “next”:

After that, we will generate UUID from PSP and store it in our DB, after data was saved we redirect the user to the payment page:

The whole flow will look like this:

UUID was used to make our request idempotent. In other words, if we will call our PSP with a method that proceeds transactions several times for a single UUID, the result will remain the same.

Hosting Credit Card pages provided by PSP

Storing credit card information is not an easy task since we need to follow complex regulations like Payment Card Industry Data Security Standard(PCI DSS) in the United States. Most companies avoid storing credit card information and instead host payment pages provided by PSP. Here is an example of Stripe checkout:

https://stripe.com/payments/checkout?embedable=true

PSP systems like Stripe got internal mechanisms of retry in case the first attempt to transfer money failed for some reason. Keeping that in mind and the fact that we will not store any sensitive data in our storage we can say that if for some reason the payment will fail we cannot automatically retry to do it once again, what we can do is check the result of payment and depending on the result make a record about this transaction consistent. For that purpose, we need a reconciliation mechanism.

Reconciliation

Here is the whole flow:

In step 4 our record in the transactions table will look like this:

id(pk)

charityID(fk)

amount

status

updated

uuid123

1

500(String)

in progress

timestamp

We will have a status field that indicates the current status of the transaction.

Notice that the amount field got type String, not double. The reason for this is that double is usually not a good choice because either software, protocols, etc. may support different numeric precisions in serialization or the number could be extremely big or extremely small.

On step 8 PSP calls our webhook and provides an acknowledgment that the transaction was completed successfully. After that, we may change the status to “completed”.

id(pk)

charityID(fk)

amount

status

updated

uuid123

1

500(String)

completed

timestamp

If something went wrong between steps 1-4, then we just not saving any data to the DB and our DB remains consistent.

If something went wrong between steps 4-8, then we may provide a reconciliation process that will periodically read the transactions table and check all transactions with status in progress and timestamp older than X and push such records to a dedicated queue so the finance team may handle it manually. Or if PSP provides some API to check transactions by ID we can make it automated.

The last thing worth mentioning here is a double-entry bookkeeping system. Great approach working with accounting, the idea behind this is that you got 2 records for each transaction, with debit and credit respectively. But in our design, this might be overkill.


Written by kliukovkin | Developer.
Published by HackerNoon on 2022/11/09