REST IN PEACE BABA
Alex Xu published a highly acclaimed book titled "Systems Design Interview: An Insider's Guide: Volume 2." It has quickly become the best-selling computer science book on Amazon in the United States, and it's well-deserved.
This book presents 13 comprehensive and original systems design challenges that are not found elsewhere. These challenges cover a wide range of topics, including developing a proximity service, implementing a nearby friends feature, designing a Google Maps-like system, building a distributed messaging queue, creating a metrics monitoring and alerting system, constructing a hotel reservation system, designing a real-time gaming leaderboard, developing a digital wallet, building a stock exchange, and many more intriguing topics.
Each chapter of the book follows a systematic four-step process:
This structured approach ensures a comprehensive understanding of the systems design process and equips readers with the necessary skills to excel in systems design interviews. The book's popularity and its unique set of challenges make it a valuable resource for computer science professionals and anyone preparing for systems design interviews.
I have read the book mentioned, explicitly focusing on the Payments System chapter. As someone who has worked on payment systems for several years at a startup company, I found this chapter particularly relevant. Building payment systems requires a good understanding of payment service, payment executor, PSP (Payment Service Provider), ledger, and wallet.
While it's impossible for a book to cover every real-life scenario encountered when building a payment system, this summary balances conciseness and provides sufficient depth. In my experience, I had to learn most of what Alex covers in the book through hands-on work on a payments system, seeking advice from others, and learning from trial and error.
This book issue focuses on the following topics covered in the original text.
Step 1: understand the problem
Step 2: high-level design
Step 3: design deep dive
Step 4: wrap up
If you find this helpful chapter, get System Design Interview: Volume 2
Payment System
In this section, we focus on the design of a payment system, considering the tremendous growth of e-commerce worldwide. Behind every successful transaction lies a robust, scalable, and adaptable payment system. It plays a crucial role in securely and efficiently exchanging monetary value.
So, what exactly is a payment system? Wikipedia says it is "any system utilized to settle financial transactions by transferring monetary value. This encompasses the institutions, instruments, individuals, rules, procedures, standards, and technologies that enable such exchanges" [1].
While the concept of a payment system may seem straightforward, it can be daunting for many developers. Even a minor error could lead to substantial financial losses and harm users' trust. However, there's no need to be alarmed! This section aims to demystify payment systems and clarify their inner workings.
A payment system can mean very different things to different people. Some may think it’s a digital wallet like Apple Pay or Google Pay. Others may think it’s a backend system that handles payments such as PayPal or Stripe. It is very important to determine the exact requirements at the beginning of the interview. These are some questions you can ask the interviewer:
Candidate: What kind of payment system are we building?
Interviewer: Assume you are building a payment backend for an e-commerce application like Amazon.com. When a customer places an order on Amazon.com, the payment system handles everything related to money movement.
Candidate: What payment options are supported? Credit cards, PayPal, bank cards, etc?
Interviewer: The payment system should support all of these options in real life. However, in this interview, we can use credit card payment as an example.
Candidate: Do we handle credit card payment processing ourselves?
Interviewer: No, we use third-party payment processors, such as Stripe, Braintree, Square, etc.
Candidate: Do we store credit card data in our system?
Interviewer: Due to extremely high security and compliance requirements, we do not store card numbers directly in our system. We rely on third-party payment processors to handle sensitive credit card data.
Candidate: Is the application global? Do we need to support different currencies and international payments?
Interviewer: Great question. Yes, the application would be global but we assume only one currency is used in this interview.
Candidate: How many payment transactions per day?
Interviewer: 1 million transactions per day.
Candidate: Do we need to support the pay-out flow, which an e-commerce site like Amazon uses to pay sellers every month?
Interviewer: Yes, we need to support that.
Candidate: I think I have gathered all the requirements. Is there anything else I should pay attention to?
Interviewer: Yes. A payment system interacts with a lot of internal services (accounting, analytics, etc.) and external services (payment service providers). When a service fails, we may see inconsistent states among services. Therefore, we need to perform reconciliation and fix any inconsistencies. This is also a requirement.
With these questions, we get a clear picture of both the functional and non-functional requirements. In this interview, we focus on designing a payment system that supports the following.
The system needs to process 1 million transactions per day, which is 1,000,000 transactions / 10^5 seconds = 10 transactions per second (TPS). 10 TPS is not a big number for a typical database, which means the focus of this system design interview is on how to correctly handle payment transactions, rather than aiming for high throughput.
To outline the payment flow, we can divide it into two main steps that align with how money moves:
Figure 1 provides a simplified visualization of the pay-in and pay-out flows.
The high-level design diagram for the pay-in flow is shown in Figure 2. Let’s take a look at each component of the system.
Payment service
The payment service plays a crucial role in accepting payment events from users and managing the payment process. One of the initial steps performed by the payment service is conducting a risk check to ensure compliance with regulations like Anti-Money Laundering and Combating the Financing of Terrorism (AML/CFT). The purpose of this risk check is to identify any signs of criminal activity, such as money laundering or terrorism financing. Payments that pass this risk check are further processed by the payment service.
Typically, the risk check service relies on a third-party provider for its implementation. This is because performing a comprehensive risk check involves intricate processes and specialized knowledge. By leveraging the expertise of a third-party provider, the risk check service can effectively assess the compliance and security aspects of payments.
Payment executor
The payment executor is responsible for carrying out individual payment orders through a Payment Service Provider (PSP). Each payment event can encompass multiple payment orders, and it is the role of the payment executor to execute them accordingly. By utilizing a Payment Service Provider, the payment executor ensures secure and reliable processing of the payment orders within the given payment event.
Payment Service Provider (PSP)
A PSP moves money from account A to account B. In this simplified example, the PSP moves the money out of the buyer’s credit card account.
Card schemes
Card schemes are the organizations that process credit card operations. Well known card schemes are Visa, MasterCard, Discovery, etc. The card scheme ecosystem is very complex [3].
Ledger
The ledger keeps a financial record of the payment transaction. For example, when a user pays the seller $1, we record it as debit $1 from a user and credit $1 to the seller. The ledger system is very important in post-payment analysis, such as calculating the total revenue of the e-commerce website or forecasting future revenue.
Wallet
The wallet keeps the account balance of the merchant. It may also record how much a given user has paid in total.
As shown in Figure 2, a typical pay-in flow works like this:
We use the RESTful API design convention for the payment service.
POST /v1/payments
This endpoint executes a payment event. As mentioned above, a single payment event may contain multiple payment orders. The request parameters are listed below:
The payment_orders look like this:
Please note that the "payment_order_id" is a globally unique identifier. When the payment executor sends a payment request to a third-party Payment Service Provider (PSP), the payment_order_id serves as the deduplication ID or idempotency key used by the PSP.
It is worth mentioning that the "amount" field in the data structure is represented as a "string" rather than a "double" data type. This choice is deliberate due to several reasons:
To address these concerns, it is recommended to keep numeric values in string format during transmission and storage. The string representation allows for precise preservation of the original value. Conversion to numeric types is typically done only when performing calculations or displaying the data.
Additionally, the API includes a GET endpoint ("GET /v1/payments/{:id}") that provides the execution status of a specific payment order based on the corresponding payment_order_id.
It's worth noting that the payment API described shares similarities with the APIs of well-known Payment Service Providers (PSPs). For a more comprehensive understanding of payment APIs, you can explore Stripe's API documentation which offers detailed information on the subject.
For the payment service, we require two tables: payment event and payment order. When selecting a storage solution for a payment system, performance is not the primary consideration. Instead, we prioritize the following factors:
In general, our preference leans towards a traditional relational database that provides ACID transaction support, rather than NoSQL or NewSQL solutions.
Regarding the payment event table, it stores comprehensive information related to payment events. Here is an example of its structure: [Provide the details of the payment event table structure.
The payment order table stores the execution status of each payment order. This is what it looks like:
Before we explore the tables, let's provide some background information:
By providing this background information, we establish the context for understanding the subsequent discussion of the tables and their related fields.
The double-entry principle, also known as double-entry accounting/bookkeeping, is a crucial design principle in the ledger system. It is essential for accurate bookkeeping and is fundamental to any payment system. This principle involves recording each payment transaction in two separate ledger accounts, both with the same amount. One account is debited, while the other is credited with the identical amount (Table 5)
According to the double-entry system, the total sum of all transaction entries should always be zero. This principle ensures that if one cent is lost, someone else gains exactly one cent. It offers end-to-end traceability and maintains consistency throughout the payment cycle. For further information on implementing the double-entry system, you can refer to Square's engineering blog, which covers the topic of an immutable double-entry accounting database service.
Many companies choose not to store credit card information internally due to the complexities associated with regulations like the Payment Card Industry Data Security Standard (PCI DSS) [8] in the United States. To avoid the burden of handling credit card information, companies opt for hosted credit card pages offered by Payment Service Providers (PSPs). These pages come in the form of a widget or an iframe for websites, and for mobile applications, they may be a pre-built page from the payment SDK. Figure 3 provides an example of the checkout process with PayPal integration. The important aspect here is that the PSP supplies a hosted payment page that directly captures the customer's card information, eliminating the need to rely on our own payment service.
The pay-out flow shares many similarities with the pay-in flow in terms of its components. However, there is a notable difference: instead of relying on a Payment Service Provider (PSP) to transfer funds from the buyer's credit card to the e-commerce website's bank account, the pay-out flow involves a third-party pay-out provider that facilitates the transfer of funds from the e-commerce website's bank account to the seller's bank account.
Typically, payment systems employ third-party account payable providers like Tipalti [9] to handle pay-outs. Pay-outs come with their own set of bookkeeping and regulatory requirements that need to be fulfilled.
This section is dedicated to enhancing the system's speed, robustness, and security. In a distributed system, errors and failures are not only expected but also frequent. For instance, what occurs if a customer accidentally clicks the "pay" button multiple times? Will they be charged multiple times? How should we handle payment failures resulting from unstable network connections? This section delves into several crucial subjects to address these concerns comprehensively.
In situations where a payment system has the capability to directly connect with banks or card schemes like Visa or MasterCard, it is possible to conduct payments without relying on a Payment Service Provider (PSP). However, these direct connections are rare and require specialized expertise. Typically, they are utilized by large companies that can justify the investment required. For the majority of companies, integrating with a PSP is the preferred approach, which can be done in two ways:
Figure 4 is used to provide a detailed explanation of how the hosted payment page functions.
For the sake of simplicity, Figure 4 excludes the payment executor, ledger, and wallet. The payment service acts as the orchestrator of the entire payment process.
The payment process begins when the user clicks the "checkout" button on the client browser. The client then sends the payment order information to the payment service.
Upon receiving the payment order information, the payment service sends a payment registration request to the Payment Service Provider (PSP). This registration request contains relevant payment details such as the amount, currency, expiration date of the payment request, and the redirect URL. To ensure unique registration and prevent duplication, a UUID field known as the nonce [10] is included. Typically, this UUID corresponds to the ID of the payment order.
The PSP returns a token to the payment service, which serves as a unique identifier for the payment registration on the PSP side. This token enables later examination of the payment registration and payment execution status.
The payment service stores the token in the database before initiating the call to the PSP-hosted payment page.
Once the token is saved, the client displays the PSP-hosted payment page. In mobile applications, PSP's SDK integration is commonly utilized for this functionality. As an example, we consider Stripe's web integration (Figure 5). Stripe offers a JavaScript library that presents the payment user interface (UI), collects sensitive payment information, and directly communicates with the PSP to complete the payment. Stripe handles the collection of sensitive payment information, ensuring it never reaches our payment system. The hosted payment page typically requires two pieces of information:
https://your-company.com/?tokenID=JIOUIQ123NSF&payResult=X324FSa
Up until now, we have discussed the ideal scenario of the hosted payment page. However, in reality, there can be instances of unreliable network connections and potential failures in any of the nine steps mentioned above. To address such failure cases in a systematic manner, reconciliation comes into play.
In scenarios where system components communicate asynchronously, there is no guarantee of message delivery or immediate response. Asynchronous communication is commonly employed in the payment industry to enhance system performance. External systems like Payment Service Providers (PSPs) and banks also prefer this type of communication. However, ensuring correctness in such cases requires the practice of reconciliation.
Reconciliation is a process that involves periodically comparing the states of related services to verify their agreement. It serves as the final line of defense in the payment system.
Each night, PSPs or banks send a settlement file to their clients. This file includes the bank account balance and all the transactions that occurred on that account during the day. The reconciliation system parses the settlement file and compares its details with the ledger system. Figure 6 provides an illustration of where the reconciliation process fits within the overall system.
Reconciliation plays a crucial role in ensuring internal consistency within the payment system. It helps identify any discrepancies that may arise between the states recorded in the ledger and the wallet.
When mismatches are detected during reconciliation, the finance team is typically responsible for making manual adjustments to resolve them. These mismatches and adjustments can generally be classified into three categories:
As previously mentioned, the payment request process involves various components and parties, both internal and external. While most payment requests are completed within seconds, there are scenarios where a payment request may experience delays, taking hours or even days to be finalized or rejected. Here are a few examples of situations that can cause longer processing times for payment requests:
The payment service must be equipped to handle these prolonged payment request processes. In cases where the buy page is hosted by an external PSP, which is common nowadays, the PSP manages these extended payment requests in the following ways:
Alternatively, some PSPs may require the payment service to actively poll the PSP for status updates on any pending payment requests, instead of relying on webhooks to receive updates.
There are two types of communication patterns that internal services use to communicate: synchronous vs asynchronous. Both are explained below.
As systems scale up, the limitations of synchronous communication, such as HTTP, become more apparent. While suitable for small-scale systems, it exhibits drawbacks as the scale increases. These drawbacks include:
Overall, as the scale of the system increases, the limitations of synchronous communication become more evident, necessitating alternative approaches for improved performance, failure handling, loose coupling, and scalability.
Asynchronous communication can be divided into two categories:
To accommodate scenarios where a request needs to be processed by multiple receivers or services, Kafka proves to be effective. In this model, each request or message is received by multiple consumers. Kafka retains the messages even after they are consumed, allowing different services to process the same message. This architecture aligns well with payment systems, where a single request may trigger multiple side effects, such as sending push notifications, updating financial reporting, and performing analytics.
Figure 11 illustrates an example of this model. Payment events are published to Kafka and subsequently consumed by various services, including the payment system itself, an analytics service, and a billing service. This design enables the seamless distribution of payment-related information across multiple services, ensuring that each service can perform its specific tasks and process the payment events accordingly.
In general, synchronous communication is characterized by its simplicity in design, but it lacks the ability to allow services to operate autonomously. As the dependency graph expands, the overall performance of the system tends to decline. On the other hand, asynchronous communication prioritizes scalability and failure resilience, albeit at the cost of design simplicity and consistency.
For a large-scale payment system that involves intricate business logic and relies on numerous third-party dependencies, opting for asynchronous communication is a more favorable choice. The complexity of such a system necessitates the ability to handle concurrent tasks, distribute workloads efficiently, and withstand failures without impacting the overall performance. By embracing asynchronous communication, the system can achieve the scalability and resilience required to effectively handle the demands and challenges of a large-scale payment ecosystem.
Every payment system has to handle failed transactions. Reliability and fault tolerance are key requirements. We review some of the techniques for tackling those challenges.
Having a definitive payment state at any stage of the payment cycle is crucial. Whenever a failure happens, we can determine the current state of a payment transaction and decide whether a retry or refund is needed. The payment state can be persisted in an append-only database table.
To gracefully handle failures, we utilize the retry queue and dead letter queue, as shown in
Figure 12.
If you are interested in a real-world example of using those queues, take a look at Uber’s payment system that utilizes Kafka to meet the reliability and fault-tolerance requirements [16].
One of the most critical issues that a payment system can encounter is double-charging a customer. Therefore, it is crucial to ensure that the design of the payment system guarantees the execution of a payment order exactly once [16].
At first glance, achieving exactly-once delivery may seem challenging, but breaking down the problem into two parts makes it more manageable. Mathematically, an operation is considered to be executed exactly once if:
We will now explain how to implement the "at-least-once" aspect using retry mechanisms, and the "at-most-once" aspect using idempotency checks.
Implementing the "at-least-once" execution can be achieved by employing retry strategies. If a payment operation fails or encounters an error, it can be retried a certain number of times until it succeeds. By ensuring that the system retries failed operations, we can guarantee that the payment order will be executed at least once.
To address the "at-most-once" execution, we utilize idempotency checks. Idempotency ensures that if the same payment order is received multiple times (due to retries or other factors), the system will recognize and handle it as a duplicate request. This prevents the system from processing the same payment order more than once, eliminating the risk of double-charging the customer.
By combining retry mechanisms for at-least-once execution and implementing idempotency checks for at-most-once execution, we can create a robust and reliable payment system that ensures the execution of payment orders exactly once, mitigating the risk of double-charging customers.
In certain cases, it becomes necessary to retry a payment transaction as a result of network errors or timeouts. Retry functionality ensures an "at-least-once" guarantee for the transaction. Figure 13 illustrates an example where a client attempts to make a $10 payment, but the payment request fails repeatedly due to a poor network connection. However, as the network eventually stabilizes, the request succeeds on the fourth attempt. This retry mechanism allows for resilient payment processing, ensuring that the transaction eventually goes through despite temporary network disruptions.
Determining the most suitable retry strategy involves careful consideration. Here are some common retry strategies:
Selecting the appropriate retry strategy can be challenging as there is no one-size-fits-all solution. However, as a general guideline, exponential backoff is effective when network issues are expected to persist for longer. Avoiding overly aggressive retry strategies is important as they can waste computing resources and potentially overload the service. Including an error code with a Retry-After header is considered a good practice.
One potential problem with retries is the possibility of double payments. Let's explore two scenarios to understand this issue further.
Here are two scenarios that highlight the need for idempotency in payment systems:
Scenario 1: The payment system integrates with a Payment Service Provider (PSP) using a hosted payment page, and the client unintentionally clicks the pay button twice. This can result in the possibility of the payment being processed twice.
Scenario 2: The payment is successfully processed by the PSP, but the response confirming the payment fails to reach our payment system due to network errors or communication issues. As a result, the user may click the "pay" button again or the client may retry the payment, potentially leading to duplicate payments.
To prevent the occurrence of double payments in such situations, it is crucial to ensure that payments are executed in an at-most-once manner. This means that even if multiple requests or retries are made, the system should only process the payment once. This guarantee of at-most-once execution is commonly referred to as idempotency. By implementing idempotency measures, payment systems can avoid duplicate charges and maintain accurate transaction records.
Idempotency plays a crucial role in ensuring the at-most-once guarantee, particularly in the context of payment systems. Idempotence is a concept rooted in mathematics and computer science, defined as operations that can be applied multiple times without altering the result beyond the initial application [18]. From an API perspective, it means that clients can make the same call repeatedly and consistently obtain the same outcome.
An idempotency key is commonly employed to facilitate communication between clients (web and mobile applications) and servers. This key is a unique value generated by the client and typically has an expiration period. Many tech companies, including Stripe [19] and PayPal [20], recommend using UUIDs as idempotency keys. In an idempotent payment request, the idempotency key is added to the HTTP header, typically as follows:
<idempotency-key: key_value>.
Now that we have a grasp of the fundamental concept of idempotency let's explore how it helps address the issue of double payments mentioned earlier. By incorporating idempotency, the system ensures that even if a payment request is inadvertently duplicated or repeated, it will not result in multiple payments being processed. The idempotency key uniquely identifies each payment request, allowing the system to recognize duplicates and respond accordingly. This prevents unintended duplicate payments, improving the reliability and integrity of the payment process.
Scenario 1: what if a customer clicks the “pay” button quickly twice?
Figure 14 demonstrates the process where, upon a user clicking "pay," an idempotency key is included in the HTTP request sent to the payment system. In the context of an e-commerce website, this idempotency key typically corresponds to the ID of the shopping cart just before the checkout.
In the case of a second request, it is considered a retry since the payment system has already encountered the idempotency key. By including the previously specified idempotency key in the request header, the payment system responds by providing the latest status of the previous request. This approach ensures that duplicate requests or retries are effectively handled and prevents unintended consequences that may arise from multiple submissions of the same payment.
If multiple concurrent requests are detected with the same idempotency key, only one request is processed and the others receive the “429 Too Many Requests” status code.
In order to maintain idempotency, the system employs the use of a unique key constraint in the database. For instance, the primary key of the database table can serve as the idempotency key. The following outlines how this process operates:
By utilizing the unique key constraint in the database, the system ensures that only one instance of a payment request with a specific idempotency key is processed. This approach maintains consistency and prevents unintended duplication of actions in the system.
Scenario 2: The payment is successfully processed by the PSP, but the response fails to reach our payment system due to network errors. Then the user clicks the “pay” again.
As shown in Figure 4 (step 2 and step 3), if the payment is successfully processed by the Payment Service Provider (PSP) but the response fails to reach our payment system due to network errors, and subsequently the user clicks the "pay" button again, the following scenario can occur:
The second "pay" request is sent to the payment system, unaware that the initial payment was actually successful.
At this point, the payment system needs to handle the duplicate request and ensure that the duplicate payment is not processed twice, preventing any unintended consequences or duplicate charges.
To address this situation, the payment system can implement mechanisms such as idempotency keys or unique identifiers. By including an idempotency key in the request, the payment system can identify and recognize the duplicate request. It can then determine that the payment has already been successfully processed and respond accordingly, without processing the payment again. This helps maintain data integrity and prevents any duplicate or erroneous transactions caused by network errors or user interactions.
Several stateful services are called in a payment execution:
In a distributed environment, the communication between any two services can fail, causing data inconsistency. Let’s take a look at some techniques to resolve data inconsistency in a payment system.
To maintain data consistency between internal services, ensuring exactly-once processing is very important.
To maintain data consistency between the internal service and external service (PSP), we usually rely on idempotency and reconciliation. If the external service supports idempotency, we should use the same idempotency key for payment retry operations. Even if an external service supports idempotent API, reconciliation is still needed because we shouldn’t assume the external system is always right.
If data is replicated, replication lag could cause inconsistent data between the primary database and the replicas. There are generally two options to solve this:
Payment security is very important. In the final part of this system design, we briefly cover a few techniques for combating cyberattacks and card thefts.
In this topic , we investigated the pay-in flow and pay-out flow. We went into great depth about retry, idempotency, and consistency. Payment error handling and security are also covered at the end of the chapter.
A payment system is extremely complex. Even though we have covered many topics, there are still more worth mentioning. The following is a representative but not an exhaustive list of relevant topics.
Congratulations on getting this far! Now give yourself a pat on the back. Good job!
[1] Payment system: https://en.wikipedia.org/wiki/Payment_system
[2] AML/CFT: https://en.wikipedia.org/wiki/Money_laundering
[3] Card scheme: https://en.wikipedia.org/wiki/Card_scheme
[4] ISO 4217: https://en.wikipedia.org/wiki/ISO_4217
[5] Stripe API Reference: https://stripe.com/docs/api
[6] Double-entry bookkeeping: https://en.wikipedia.org/wiki/Double-entry_bookkeeping
[7] Books, an immutable double-entry accounting database service:
https://developer.squareup.com/blog/books-an-immutable-double-entry-accounting-database-service/
[8] Payment Card Industry Data Security Standard: https://en.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard
[9] Tipalti: https://tipalti.com
[10] Nonce: https://en.wikipedia.org/wiki/Cryptographic_nonce
[11] Webhooks: https://stripe.com/docs/webhooks
[12] Customize your success page: https://stripe.com/docs/payments/checkout/custom-success-page
[13] 3D Secure: https://en.wikipedia.org/wiki/3-D_Secure
[14] Kafka Connect Deep Dive – Error Handling and Dead Letter Queues: https://www.confluent.io/blog/kafka-connect-deep-dive-error-handling-dead-letter-queues/
[15] Reliable Processing in a Streaming Payment System:
[16] Chain Services with Exactly-Once Guarantees: https://www.confluent.io/blog/chain-services-exactly-guarantees/
[17] Exponential backoff: https://en.wikipedia.org/wiki/Exponential_backoff
[18] Idempotence: https://en.wikipedia.org/wiki/Idempotence
[19] Stripe idempotent requests: https://stripe.com/docs/api/idempotent_requests
[20] Idempotency: https://developer.paypal.com/docs/platforms/develop/idempotency/
[21] Paxos: https://en.wikipedia.org/wiki/Paxos_(computer_science)
[22] Raft: https://raft.github.io
[23] YogabyteDB: https://www.yugabyte.com
[24] Cockroachdb: https://www.cockroachlabs.com
[25] What is DDoS attack: https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/
[26] How Payment Gateways Can Detect and Prevent Online Fraud: https://www.chargebee.com/blog/optimize-online-billing-stop-online-fraud/
[27] Advanced Technologies for Detecting and Preventing Fraud at Uber: https://eng.uber.com/advanced-technologies-detecting-preventing-fraud-uber/
[28] Re-Architecting Cash and Digital Wallet Payments for India with Uber Engineering: https://eng.uber.com/india-payments/
[29] Scaling Airbnb’s Payment Platform: https://medium.com/airbnb-engineering/scaling-airbnbs-payment-platform-43ebfc99b324
Comments