logo

TRAN DUY THIEN

09/05/2025

Payment System

Trong những năm gần đây, E-commerce đã nhanh chóng trở nên phổ biến trên toàn cầu. Mỗi Transaction đều phụ thuộc vào một Payment System hoạt động phía sau. Một Payment System đáng tin cậy, Scalable, và Flexible là vô cùng quan trọng.

Theo Wikipedia, “Payment System là bất kỳ hệ thống nào thực hiện chuyển giao giá trị tiền tệ để thanh toán Financial Transactions. Điều này bao gồm các Institutions, Instruments, People, Rules, Procedures, Standards, và Technologies cho phép trao đổi” [1].

Payment System có vẻ dễ hiểu trên bề mặt, nhưng đối với nhiều Developers, việc triển khai lại rất khó khăn. Một sai lầm nhỏ có thể dẫn đến Significant Revenue Losses và Undermine User Trust. Nhưng đừng lo! Trong chương này, chúng ta sẽ làm sáng tỏ bí ẩn của Payment System.

Step 1 – Understand the Problem and Establish Design Scope

Payment System có ý nghĩa khác nhau đối với từng người. Một số có thể nghĩ đó là Digital Wallet như Apple Pay hoặc Google Pay. Những người khác có thể xem nó là Backend System xử lý Payments, như PayPal hoặc Stripe. Việc xác định Exact Requirements ngay từ đầu là rất quan trọng. Dưới đây là một số câu hỏi bạn có thể hỏi Interviewer:

  • Candidate: Chúng ta đang xây dựng Payment System loại nào?
    Interviewer: Giả sử bạn đang xây dựng Payment Backend cho E-commerce Application như Amazon. Khi khách hàng đặt hàng trên Amazon, Payment System xử lý tất cả Affairs liên quan đến Funds Flow.
  • Candidate: Hỗ trợ Payment Methods nào? Credit Card, PayPal, Debit Card, v.v.?
    Interviewer: Trong thực tế, Payment System nên hỗ trợ tất cả các Payment Methods này. Tuy nhiên, trong Interview này, chúng ta có thể sử dụng Credit Card Payments làm ví dụ.
  • Candidate: Chúng ta tự xử lý Credit Card Payments không?
    Interviewer: Không, chúng ta sử dụng Third-Party Payment Processor, như Stripe, Braintree, Square, v.v.
  • Candidate: Hệ thống của chúng ta có lưu trữ Credit Card Data không?
    Interviewer: Do High Security và Compliance Requirements, chúng ta không trực tiếp lưu trữ Card Numbers trong hệ thống. Chúng ta dựa vào Third-Party Payment Processor để xử lý Sensitive Credit Card Data.
  • Candidate: Ứng dụng này có Global không? Chúng ta cần hỗ trợ Different Currencies và International Payments không?
    Interviewer: Câu hỏi hay. Có, ứng dụng là Global, nhưng trong Interview này, chúng ta giả sử chỉ sử dụng One Currency.
  • Candidate: Có bao nhiêu Payment Transactions mỗi ngày?
    Interviewer: 1 triệu Transactions mỗi ngày.
  • Candidate: Chúng ta cần hỗ trợ Payment Flow như Amazon trả tiền hàng tháng cho Sellers không?
    Interviewer: Có, chúng ta cần hỗ trợ.
  • Candidate: Tôi nghĩ đã thu thập đủ Requirements. Có điều gì cần lưu ý thêm không?
    Interviewer: Có. Payment System tương tác với nhiều Internal Services (Accounting, Analytics, v.v.) và External Services (Payment Service Providers). Khi Service Fails, chúng ta có thể thấy State Inconsistencies giữa Services. Vì vậy, chúng ta cần thực hiện Reconciliation và Fix Any Inconsistencies. Đây cũng là Requirement.

Qua các câu hỏi này, chúng ta hiểu rõ Functional và Non-Functional Requirements. Trong Interview này, chúng ta tập trung vào thiết kế Payment System hỗ trợ:

Functional Requirements

  • Payment Flow: Payment System thu tiền từ Customers thay mặt Sellers.
  • Payout Flow: Payment System gửi tiền đến Sellers trên toàn cầu.

Non-Functional Requirements

  • Reliability và Fault Tolerance. Payment Failures cần được xử lý cẩn thận.
  • Cần Reconciliation Process giữa Internal Services (Payment System, Accounting System) và External Services (Payment Service Providers). Quy trình này Asynchronously Verifies Consistency của Payment Information giữa các hệ thống.

Back-of-the-Envelope Estimation

Hệ thống cần xử lý 1 triệu Transactions mỗi ngày, tức là 1,000,000 Transactions / 10^5 giây = 10 Transactions Per Second (TPS). Đối với Typical Database, 10 TPS không phải là Large Number, nghĩa là System Design Interview này tập trung vào Correctly Handling Payment Transactions, không phải High Throughput.

Step 2 – Propose High-Level Design and Get Buy-In

Từ High-Level Perspective, Payment Flow được chia thành hai bước để phản ánh Funds Flow:

  • Payment Flow
  • Payout Flow

Lấy ví dụ E-commerce Website Amazon, khi Buyer đặt hàng, tiền chảy vào Amazon’s Bank Account, đây là Payment Flow. Mặc dù tiền ở Amazon’s Bank Account, Amazon không sở hữu toàn bộ số tiền, Sellers sở hữu phần lớn, Amazon chỉ đóng vai trò Funds Custodian, thu một Fee. Sau đó, khi Product Shipped và Funds Released, Balance sau khi trừ Fee sẽ chảy từ Amazon’s Bank Account đến Seller’s Bank Account, đây là Payout Flow. Simplified Payment và Payout Flow được minh họa trong Hình 1.

Payment and Payout Flow

Payment Flow

Hình 2 hiển thị High-Level Design của Payment Flow. Hãy xem xét từng Component của hệ thống.

Payment Flow Design

Payment Service

Payment Service nhận User’s Payment Events và Coordinates Payment Flow. Nó thường thực hiện Risk Check trước, đánh giá Compliance với Regulations như Anti-Money Laundering/Anti-Terrorism Financing [2], và kiểm tra Evidence của Criminal Activities như Money Laundering hoặc Terrorism Financing. Payment Service chỉ xử lý Payments qua Risk Check này. Thông thường, Risk Check Service sử dụng Third-Party Provider vì nó Very Complex và Highly Specialized.

Payment Executor

Payment Executor thực thi Single Payment Order thông qua Payment Service Provider (PSP). Một Payment Event có thể chứa Multiple Payment Orders.

Payment Service Provider (PSP)

PSP chuyển Funds từ Account A đến Account B. Trong ví dụ đơn giản này, PSP chuyển Funds từ Buyer’s Credit Card Account.

Card Scheme

Card Scheme là Organization xử lý Credit Card Operations. Notable Card Schemes bao gồm Visa, MasterCard, Discover, v.v. Card Scheme Ecosystem rất Complex [3].

Ledger

Ledger giữ Financial Records của Payment Transactions. Ví dụ, khi User trả 1 USD cho Seller, Ledger Records Debit 1 USD từ User và Credit 1 USD vào Seller’s Account. Ledger System rất quan trọng trong Post-Payment Analysis, như tính Total Revenue của E-commerce Website hoặc Predicting Future Revenue.

Wallet

Wallet giữ Merchant’s Account Balance. Nó cũng có thể ghi lại Total Amount Paid bởi Specific User.

Như Hình 2, Typical Payment Flow như sau:

  1. Khi User nhấn “Place Order” Button, Payment Event được tạo và gửi đến Payment Service.
  2. Payment Service lưu Payment Event trong Database.
  3. Đôi khi, Single Payment Event có thể chứa Multiple Payment Orders. Ví dụ, bạn có thể chọn Products từ Multiple Sellers trong One Checkout. Nếu E-commerce Website tách Checkout thành Multiple Payment Orders, Payment Service gọi Payment Executor cho mỗi Payment Order.
  4. Payment Executor lưu Payment Instructions trong Database.
  5. Payment Executor gọi External PSP để xử lý Credit Card Payment.
  6. Sau khi Payment Executor xử lý Payment thành công, Payment Service cập nhật Wallet để ghi lại Specific Seller có bao nhiêu tiền.
  7. Wallet Server lưu Updated Balance Information trong Database.
  8. Sau khi Wallet Service cập nhật Seller Balance thành công, Payment Service gọi Ledger để Update.
  9. Ledger Service thêm New Ledger Information vào Database.

Payment Service API

Chúng ta sử dụng RESTful API Design Convention cho Payment Service.

POST /v1/payments

Endpoint này thực thi Payment Event, như đã đề cập, Single Payment Event có thể chứa Multiple Payment Orders. Request Parameters như sau:

Field Description Type
buyer_info Thông tin Buyer JSON
checkout_id Globally Unique ID của Checkout này String
credit_card_info Có thể là Encrypted Credit Card Info hoặc Payment Token. Giá trị này là PSP-Specific. JSON
payment_orders Danh sách Payment Orders List

Table 1: API Request Parameters (Execute Payment Event)

payment_orders như sau:

Field Description Type
seller_account Seller nào sẽ nhận tiền String
amount Số tiền Transaction của Order String
currency Currency của Order String (ISO 4217 [4])
payment_order_id Globally Unique ID của Payment này String

Table 2: payment_orders

Lưu ý, payment_order_id là Globally Unique, khi Payment Executor gửi Payment Request đến Third-Party PSP, payment_order_id được sử dụng bởi PSP như Deduplication ID, còn gọi là Idempotency Key.

Bạn có thể nhận thấy trường amount có Data Type là String, không phải Double. Double không phải là Good Choice vì:

  1. Different Protocols, Software, và Hardware có thể hỗ trợ Different Numerical Precision trong Serialization và Deserialization, dẫn đến Unexpected Rounding Errors.
  2. Number có thể Very Large (ví dụ, Japan’s GDP năm 2020 khoảng 5×10^14 Yen) hoặc Very Small (ví dụ, 1 Satoshi của Bitcoin là 10^-8).

Đề xuất giữ Numbers ở String Format trong Transmission và Storage, chỉ Parse thành Numbers khi Displaying hoặc Calculating.

GET /v1/payments/{:id}

Endpoint này trả về Execution Status của Single Payment Order dựa trên payment_order_id.

Payment API trên tương tự với API của một số Well-Known PSPs. Nếu bạn muốn hiểu Comprehensive về Payment APIs, xem Stripe’s API Documentation [5].

Payment Service Data Model

Payment Business cần hai Tables: Payment Events Table và Payment Orders Table. Khi chọn Storage Solution cho Payment System, Performance thường không phải là Top Priority, chúng ta ưu tiên:

  1. Proven Stability. Storage System có được sử dụng bởi Other Large Financial Companies trong nhiều năm (ví dụ, hơn 5 năm) và nhận Positive Feedback không.
  2. Rich Tooling Support, như Monitoring và Investigation Tools.
  3. Database Administrator (DBA) Job Market Maturity. Việc có thể Hire Experienced DBAs là Very Important Consideration.

Thông thường, chúng ta thích Traditional Relational Databases với ACID Transaction Support hơn NoSQL/NewSQL.

Payment Events Table chứa Detailed Payment Event Information. Nó như sau:

Name Type
checkout_id String PK
buyer_info String
seller_info String
credit_card_info Depends on Card Provider
is_payment_done Boolean

Table 3: Payment Events

Payment Orders Table lưu trữ Execution Status của mỗi Payment Order. Nó như sau:

Name Type
payment_order_id String PK
buyer_account String
amount String
currency String
checkout_id String FK
payment_order_status String
ledger_updated Boolean
wallet_updated Boolean

Table 4: Payment Orders

Trước khi đi sâu vào Tables, hãy xem một số Background Information:

  • checkout_id là Foreign Key. Single Checkout tạo One Payment Event, có thể chứa Multiple Payment Orders.
  • Khi gọi Third-Party PSP để Debit từ Buyer’s Credit Card, tiền không chuyển trực tiếp đến Seller’s Account, mà đến E-commerce Website’s Bank Account, quá trình này gọi là Payment. Khi Payment Conditions Satisfied, như Goods Shipped, Seller khởi tạo Payout, tiền mới chuyển từ E-commerce Website’s Bank Account đến Seller’s Bank Account. Vì vậy, trong Payment Flow, chúng ta chỉ cần Buyer’s Card Information, không cần Seller’s Bank Account Information.

Trong Payment Orders Table (Bảng 4), payment_order_status là Enum Type, lưu Execution Status của Payment Order, bao gồm NOT_STARTED, EXECUTING, SUCCESS, FAILED. Update Logic như sau:

  1. payment_order_status khởi đầu là NOT_STARTED.
  2. Khi Payment Service gửi Payment Order đến Payment Executor, payment_order_statusEXECUTING.
  3. Payment Service cập nhật payment_order_status thành SUCCESS hoặc FAILED dựa trên Payment Executor’s Response.

Khi payment_order_statusSUCCESS, Payment Service gọi Wallet Service để Update Seller Balance, và cập nhật trường wallet_updated thành TRUE. Ở đây, chúng ta Simplify Design bằng cách giả sử Wallet Update luôn thành công.

Sau đó, Payment Service gọi Ledger Service để Update Ledger Database bằng cách cập nhật trường ledger_updated thành TRUE.

Khi tất cả Payment Orders dưới cùng checkout_id được xử lý thành công, Payment Service cập nhật is_payment_done trong Payment Events Table thành TRUE. Scheduled Job thường chạy ở Fixed Intervals để Monitor Status của Ongoing Payment Orders. Khi Payment Order không hoàn thành trong Threshold, nó sẽ Trigger Alert để Engineers điều tra.

Double-Entry Bookkeeping System

Ledger System có Very Important Design Principle: Double-Entry Bookkeeping Principle (còn gọi là Double-Entry Bookkeeping [6]). Double-Entry Bookkeeping System là Foundation của bất kỳ Payment System và Key để Accurate Accounting. Nó ghi lại mỗi Payment Transaction vào Two Separate Ledger Entries với Equal Amounts. Một Account được Debited, Account kia được Credited với Same Amount (Bảng 5).

Account Debit Credit
Buyer 1 USD
Seller 1 USD

Table 5: Double-Entry Bookkeeping

Double-Entry Bookkeeping System yêu cầu Total Sum của tất cả Transaction Entries phải là 0. Một Penny Lost nghĩa là Someone Else Gained a Penny. Nó cung cấp End-to-End Traceability và đảm bảo Consistency suốt Payment Cycle. Để tìm hiểu thêm về Implementing Double-Entry Bookkeeping System, xem Square’s Engineering Blog về Immutable Double-Entry Bookkeeping Database Service [7].

Hosted Payment Page

Hầu hết Companies không muốn Store Credit Card Information Internally, vì nếu làm vậy, họ phải xử lý Complex Regulations, như Payment Card Industry Data Security Standard (PCI DSS) [8] ở Mỹ. Để Avoid Handling Credit Card Information, Companies sử dụng Hosted Credit Card Page do PSP cung cấp. Đối với Website, đó là Widget hoặc iFrame, đối với Mobile Apps, có thể là Pre-Built Page từ Payment SDK. Hình 3 hiển thị ví dụ Checkout Experience với PayPal Integration. Key Point là PSP cung cấp Hosted Payment Page để Directly Capture Customer’s Card Information, không dựa vào Payment Service của chúng ta.

Hosted Payment Page with PayPal

Payout Flow

Các Components của Payout Flow rất giống Payment Flow. Một Difference là Payout Flow không sử dụng PSP để chuyển tiền từ Buyer’s Credit Card đến E-commerce Website’s Bank Account, mà sử dụng Third-Party Accounts Payable Provider để chuyển tiền từ E-commerce Website’s Bank Account đến Seller’s Bank Account.

Thông thường, Payment System sử dụng Third-Party Providers như Tipalti [9] để xử lý Payouts. Payouts cũng có nhiều Accounting và Regulatory Requirements.

Step 3 – Deep Dive

Trong phần này, chúng ta tập trung vào làm hệ thống Faster, More Reliable, và More Secure. Trong Distributed System, Errors và Failures không chỉ Inevitable mà còn Common. Ví dụ, nếu Customer nhấn “Pay” Button nhiều lần, điều gì xảy ra? Họ có bị Charged Multiple Times không? Làm sao xử lý Payment Failures do Poor Network Connectivity? Trong phần này, chúng ta sẽ đi sâu vào các Key Topics:

  • PSP Integration
  • Reconciliation
  • Handling Payment Delays
  • Communication Between Internal Services
  • Handling Payment Failures
  • Exactly-Once Delivery
  • Consistency
  • Security

PSP Integration

Nếu Payment System có thể Directly Connect đến Banks hoặc Card Schemes như Visa hoặc MasterCard, nó có thể Process Payments mà không cần PSP. Direct Connections này không phổ biến và Very Specialized. Chúng thường chỉ dành cho Truly Large Companies có thể Justify Investment. Đối với hầu hết Companies, Payment System tích hợp với PSP qua một trong hai cách:

  1. Nếu Company có thể Securely Store Sensitive Payment Information và chọn làm vậy, nó có thể sử dụng API Integration với PSP. Company chịu trách nhiệm phát triển Payment Webpage, Collecting và Storing Sensitive Payment Information. PSP chịu trách nhiệm Connecting đến Banks hoặc Card Schemes.
  2. Nếu Company chọn không Store Sensitive Payment Information do Complex Regulations và Security Issues, PSP cung cấp Hosted Payment Page để Collect Card Payment Details và Securely Store chúng trong PSP. Đây là phương pháp hầu hết Companies sử dụng.

Chúng ta sử dụng Hình 4 để giải thích chi tiết cách Hosted Payment Page hoạt động.

Hosted Payment Page Workflow

Để đơn giản, chúng ta bỏ qua Payment Executor, Ledger, và Wallet trong Hình 4. Payment Service chịu trách nhiệm Coordinating Entire Payment Flow.

  1. User nhấn “Checkout” Button trong Client Browser. Client gọi Payment Service với Payment Order Information.
  2. Payment Service, sau khi nhận Payment Order Information, gửi Payment Registration Request đến PSP. Registration Request chứa Payment Information, như Amount, Currency, Expiry Date của Payment Request, và Redirect URL. Vì Payment Order chỉ có thể Register Once, có UUID Field để đảm bảo Only Registered Once. UUID này thường là Payment Order ID.
  3. PSP trả về Token cho Payment Service. Token là UUID trên PSP Side, Uniquely Identifying Payment Registration. Chúng ta có thể dùng Token này sau để Check Payment Registration và Payment Execution Status.
  4. Payment Service lưu Token trong Database trước khi gọi PSP’s Hosted Payment Page.
  5. Một khi Token được Persisted, Client hiển thị PSP Hosted Payment Page. Mobile Apps thường sử dụng PSP’s SDK Integration để đạt được điều này. Ở đây, chúng ta sử dụng Stripe’s Web Integration làm ví dụ (Hình 5). Stripe cung cấp JavaScript Library để Display Payment UI, Collect Sensitive Payment Information, và Directly Call PSP để Complete Payment. Sensitive Payment Information được Stripe thu thập, Never Reaches Our Payment System. Hosted Payment Page thường cần hai Pieces of Information:
    • Token nhận được ở bước 4. PSP’s JavaScript Code sử dụng Token để Retrieve Payment Request Details từ PSP Backend. Important Information là Amount to Charge.
    • Redirect URL. Đây là Webpage URL được gọi sau khi Payment Completed. Khi PSP’s JavaScript Completes Payment, nó Redirects Browser đến Redirect URL. Thông thường, Redirect URL là E-commerce Webpage hiển thị Checkout Status. Lưu ý, Redirect URL khác với Webhook URL [11] ở bước 9.

    Stripe Hosted Payment Page

  6. User điền Payment Information trên PSP’s Webpage, như Credit Card Number, Cardholder Name, Expiry Date, v.v., rồi nhấn Pay Button, PSP bắt đầu Process Payment.
  7. PSP trả về Payment Status.
  8. Webpage giờ Redirect đến Redirect URL. Payment Status nhận được ở bước 7 thường được Attached vào URL. Ví dụ, Full Redirect URL có thể là [12]: https://your-company.com/?tokenID=JIOUIQ123NSF&payResult=X324FSa
  9. PSP Asynchronously Calls Payment Service qua Webhook để lấy Payment Status. Webhook là URL trên Payment System Side, đã Registered với PSP trong Initial Setup. Khi Payment System nhận Payment Event qua Webhook, nó Extracts Payment Status và Updates payment_order_status Field trong Payment Orders Database Table.

Đến đây, chúng ta đã giải thích Happy Path của Hosted Payment Page. Thực tế, Network Connections có thể Unreliable, tất cả 9 Steps trên đều có thể Fail. Có Systematic Way để Handle Failure Cases không? Câu trả lời là Reconciliation.

Reconciliation

Khi System Components giao tiếp Asynchronously, không có Guarantee rằng Messages sẽ Delivered, hoặc Responses sẽ Returned. Điều này rất Common trong Payment Business, thường sử dụng Asynchronous Communication để Improve System Performance. External Systems (như PSP hoặc Banks) cũng thích Asynchronous Communication. Vậy trong trường hợp này, chúng ta đảm bảo Correctness như thế nào?

Câu trả lời là Reconciliation. Đây là Practice của Periodically Comparing States giữa Related Services để Verify Consistency. Nó thường là Last Line of Defense của Payment System.

Mỗi tối, PSP hoặc Bank gửi Settlement File cho Clients. Settlement File chứa Bank Account Balance và All Transactions Occurred on That Bank Account trong ngày. Reconciliation System Parses Settlement File và Compares Details với Ledger System. Hình 6 hiển thị vị trí của Reconciliation Flow trong System.

Reconciliation Flow

Reconciliation cũng dùng để Verify Internal Consistency trong Payment System. Ví dụ, Ledger và Wallet States có thể Different, chúng ta có thể sử dụng Reconciliation System để Detect Any Differences.

Để Fix Mismatches phát hiện trong Reconciliation, chúng ta thường dựa vào Finance Team để Manual Adjustments. Mismatches và Adjustments thường được chia thành ba Categories:

  1. Mismatch là Classifiable, Adjustment có thể Automated. Trong trường hợp này, chúng ta biết Cause của Mismatch, biết cách Fix, và viết Program để Automatically Adjust là Cost-Effective. Engineers có thể Automate Mismatch Classification và Adjustment.
  2. Mismatch là Classifiable, nhưng chúng ta không thể Automate Adjustment. Trong trường hợp này, chúng ta biết Cause của Mismatch và cách Fix, nhưng Cost của Writing Automated Adjustment Program quá cao. Mismatch được đưa vào Work Queue, Finance Team Manually Fixes Mismatch.
  3. Mismatch không thể Classifiable. Trong trường hợp này, chúng ta không biết How Mismatch Occurred. Mismatch được đưa vào Special Work Queue. Finance Team Manually Investigates.

Handling Payment Delays

Như đã đề cập, End-to-End Payment Request đi qua Many Components, liên quan đến Internal và External Parties. Mặc dù hầu hết trường hợp, Payment Request hoàn thành trong Few Seconds, trong một số trường hợp, Payment Request Stalls, đôi khi mất Hours hoặc Days để Complete hoặc Reject. Dưới đây là một số ví dụ Payment Request có thể Take Longer Than Usual:

  • PSP xem Payment Request là High Risk, yêu cầu Manual Review.
  • Credit Card cần Extra Protection, như 3D Secure Authentication [13], yêu cầu Cardholder cung cấp Additional Details để Verify Purchase.

Payment Service phải Handle These Long-Running Payment Requests. Nếu Purchase Page được Hosted bởi External PSP (rất Common ngày nay), PSP sẽ Handle These Long-Running Payment Requests qua:

  • PSP trả về Pending Status cho Client. Client hiển thị Status này cho User. Client cũng cung cấp Page để User Check Current Payment Status.
  • PSP Tracks Pending Payments thay mặt chúng ta, và Notifies Payment Service về Any Status Updates qua Webhook Registered với PSP.

Khi Payment Request cuối cùng Complete, PSP gọi Registered Webhook đã đề cập. Payment Service Updates Internal Systems và Completes Shipment đến Customer.

Hoặc, một số PSP không Update Payment Service qua Webhook, mà đặt Burden lên Payment Service để Poll PSP cho Any Pending Payment Request Status Updates.

Communication Between Internal Services

Internal Services giao tiếp sử dụng hai Communication Patterns: Synchronous và Asynchronous. Dưới đây sẽ giải thích cả hai Patterns.

Synchronous Communication

Synchronous Communication như HTTP hoạt động tốt cho Small-Scale Systems, nhưng khi Scale Increases, Drawbacks trở nên rõ ràng. Nó tạo Long Request-Response Cycle phụ thuộc vào Many Services. Drawbacks của Approach này là:

  • Poor Performance. Nếu Any Service trong Chain Performs Poorly, Entire System bị ảnh hưởng.
  • Poor Fault Isolation. Nếu PSP hoặc Any Other Service Fails, Client sẽ không nhận Response.
  • Tight Coupling. Request Sender cần biết Receiver.
  • Hard to Scale. Nếu không sử dụng Queue làm Buffer, rất khó Scale System để Support Sudden Traffic Spikes.

Asynchronous Communication

Asynchronous Communication được chia thành hai Categories:

  • Single Receiver: Mỗi Request (Message) được xử lý bởi One Receiver hoặc Service. Nó thường được triển khai qua Shared Message Queue. Message Queue có thể có Multiple Subscribers, nhưng khi Message được Processed, nó được Removed từ Queue. Hãy xem ví dụ cụ thể. Trong Hình 9, Service A và Service B đều Subscribe vào Shared Message Queue. Khi m1 và m2 được Consumed bởi Service A và Service B, cả hai Messages đều bị Removed từ Queue, như Hình 10.

Single Receiver Message Queue
Message Consumption in Single Receiver

  • Multiple Receivers: Mỗi Request (Message) được xử lý bởi Multiple Receivers hoặc Services. Kafka hoạt động tốt ở đây. Khi Consumer Receives Message, nó không Remove Message từ Kafka. Same Message có thể được Processed bởi Different Services. Model này Map Well vào Payment System, vì Same Request có thể Trigger Multiple Side Effects, như Sending Push Notifications, Updating Financial Reports, Analytics, v.v. Hình 11 hiển thị ví dụ. Payment Event được Published đến Kafka và Consumed bởi Different Services (như Payment System, Analytics Service, và Billing Service).

Multiple Receivers with Kafka

Nói chung, Synchronous Communication có Simpler Design, nhưng nó không cho phép Service Autonomy. Khi Dependency Graph Grows, Overall Performance bị ảnh hưởng. Asynchronous Communication Trade-Off Design Simplicity và Consistency để lấy Scalability và Fault Resilience. Đối với Large Payment Systems với Complex Business Logic và Many Third-Party Dependencies, Asynchronous Communication là Better Choice.

Handling Payment Failures

Mỗi Payment System phải Handle Failed Transactions. Reliability và Fault Tolerance là Key Requirements. Chúng ta đã xem xét một số Techniques để Address These Challenges.

Tracking Payment Status

Having Clear Payment Status tại Any Stage của Payment Cycle là Crucial. Mỗi khi Failure Occurs, chúng ta có thể Determine Current State của Payment Transaction và Decide Whether to Retry hoặc Refund. Payment Status có thể được Stored trong Append-Only Database Table.

Retry Queue và Dead Letter Queue

Để Properly Handle Failures, chúng ta sử dụng Retry Queue và Dead Letter Queue, như Hình 12.

Retry and Dead Letter Queues

  • Retry Queue: Retryable Errors (như Transient Errors) sẽ được Routed đến Retry Queue.
  • Dead Letter Queue [14]: Nếu Message Repeatedly Fails, nó cuối cùng sẽ vào Dead Letter Queue. Dead Letter Queue được dùng để Debug và Isolate Problematic Messages để Inspect Why They Were Not Successfully Processed.
  1. Kiểm tra Failure có Retryable không.
    • 1a. Retryable Failures được Routed đến Retry Queue.
    • 1b. Đối với Non-Retryable Failures (như Invalid Input), Error được Stored trong Database.
  2. Payment System sử dụng Events trong Retry Queue và Retries Failed Payment Transactions.
  3. Nếu Payment Transaction Fails Again:
    • 3a. Nếu Retry Count chưa Exceed Threshold, Event được Routed đến Retry Queue.
    • 3b. Nếu Retry Count Exceeds Threshold, Event được đưa vào Dead Letter Queue. Failed Events này có thể cần Investigation.

Nếu bạn muốn xem Real-World Example sử dụng Queues này, xem Uber’s Payment System, sử dụng Kafka để Meet Reliability và Fault Tolerance Requirements [16].

Exactly-Once Delivery

Một trong Most Severe Issues mà Payment System có thể gặp là Charging Customers Multiple Times. Trong Design của chúng ta, Ensuring Payment System Executes Payment Order Exactly Once là Very Important [16].

Thoạt nhìn, Exactly-Once Delivery có vẻ Difficult to Solve, nhưng nếu chia Problem thành Two Parts, nó dễ hơn nhiều. Mathematically, Operation được Executed Exactly Once nếu thỏa mãn:

  1. Nó được Executed At Least Once.
  2. Đồng thời, nó được Executed At Most Once.

Chúng ta sẽ giải thích cách sử dụng Retry để Achieve At Least Once, và Idempotency Check để Achieve At Most Once.

Retry

Đôi khi, do Network Errors hoặc Timeouts, chúng ta cần Retry Payment Transactions. Retry cung cấp At Least Once Guarantee. Ví dụ, như Hình 13, Client cố gắng Pay 10 USD, nhưng do Poor Network Connection, Payment Request liên tục Fails. Trong ví dụ này, Network cuối cùng Recovers, Request Succeeds ở Fourth Attempt.

Retry Mechanism

Xác định Appropriate Time Interval giữa Retries rất quan trọng. Dưới đây là một số Common Retry Strategies:

  • Immediate Retry: Client Immediately Resends Request.
  • Fixed Interval: Wait Fixed Amount of Time giữa Payment Failure và Retry.
  • Incremental Interval: Client Waits Short Time cho First Retry, rồi Gradually Increases Time cho Subsequent Retries.
  • Exponential Backoff [17]: Double Wait Time giữa Retries sau mỗi Failure. Ví dụ, khi Request Fails lần đầu, Retry sau 1 giây; nếu Fail lần hai, Wait 2 giây trước Retry tiếp theo; nếu Fail lần ba, Wait 4 giây.
  • Cancel: Client có thể Cancel Request. Common khi Failure là Permanent hoặc Repeated Requests không Likely to Succeed.

Xác định Right Retry Strategy là Difficult. Không có One-Size-Fits-All Solution. General Guideline là sử dụng Exponential Backoff nếu Network Issues không Likely to Resolve Quickly. Overly Aggressive Retry Strategies lãng phí Computing Resources và có thể Cause Service Overload. Good Practice là cung cấp Error Code với Retry-After Header.

Potential Issue của Retry là Duplicate Payments. Hãy xem xét hai Scenarios:

  • Scenario 1: Payment System sử dụng Hosted Payment Page với PSP Integration, Customer nhấn Pay Button Twice.
  • Scenario 2: Payment Successfully Processed bởi PSP, nhưng do Network Error, Response không Reach Our Payment System, User nhấn “Pay” Button Again hoặc Client Retries Payment.

Để Avoid Duplicate Payments, Payment phải Executed At Most Once. At Most Once Guarantee này còn gọi là Idempotency.

Idempotency

Idempotency là Key để Ensure At Most Once Guarantee. Theo Wikipedia, “Idempotency là Property của Certain Operations trong Mathematics và Computer Science, có thể Applied Multiple Times mà không thay đổi Result ngoài Initial Application” [18]. Từ API Perspective, Idempotency nghĩa là Client có thể Repeat Same Call và Produce Same Result.

Đối với Client (Web và Mobile Apps) và Server Communication, Idempotency Key thường là Client-Generated Unique Value, Expires sau Certain Time. UUID thường được dùng làm Idempotency Key, nhiều Tech Companies (như Stripe [19] và PayPal [20]) đề xuất sử dụng nó. Để Execute Idempotent Payment Request, cần Add Idempotency Key trong HTTP Header: idempotency-key: key_value.

Giờ chúng ta đã hiểu Idempotency Basics, hãy xem nó giúp giải quyết Double Payment Problem như thế nào.

Scenario 1: Nếu Customer Nhanh Chóng Nhấn Pay Button Hai Lần?

Trong Hình 14, khi User nhấn “Pay”, Idempotency Key được gửi như Part của HTTP Request đến Payment System. Trong E-commerce Website, Idempotency Key thường là Shopping Cart ID trước Checkout.

Đối với Second Request, nó được xem là Retry, vì Payment System đã thấy Idempotency Key. Khi chúng ta Include Previously Specified Idempotency Key trong Request Header, Payment System trả về Latest Status của Previous Request.

Idempotency Handling Duplicate Request

Nếu Detect Multiple Concurrent Requests với Same Idempotency Key, chỉ One Request được Processed, Other Requests nhận 429 Too Many Requests Status Code.

Để Support Idempotency, chúng ta có thể sử dụng Database’s Unique Key Constraint. Ví dụ, Primary Key của Database Table được dùng làm Idempotency Key. Nó hoạt động như sau:

  1. Khi Payment System nhận Payment, nó cố gắng Insert Row vào Database Table.
  2. Successful Insertion nghĩa là chúng ta chưa thấy Payment Request này trước đây.
  3. Nếu Insertion Fails do Same Primary Key đã tồn tại, nghĩa là chúng ta đã thấy Payment Request này trước đây. Second Request sẽ không được Processed.
Scenario 2: Payment Successfully Processed bởi PSP, nhưng do Network Error, Response không Reach Our Payment System, User Nhấn “Pay” Lại.

Như Hình 4 (bước 2 và 3), Payment Service gửi Nonce đến PSP, PSP trả về Corresponding Token, Nonce Uniquely Represents Payment Order, Token Uniquely Maps đến Nonce, do đó Token cũng Uniquely Maps đến Payment Order.

Khi User nhấn Pay Button lại, Payment Order là Same, do đó Token gửi đến PSP cũng Same, vì Token được sử dụng như Idempotency Key trên PSP Side, nó có thể Recognize Duplicate Payment và Return Status của Previous Execution.

Consistency

Trong Payment Execution, Several Stateful Services được gọi:

  1. Payment Service lưu Payment-Related Data, như Nonce, Token, Payment Orders, Execution Status, v.v.
  2. Ledger lưu All Accounting Data.
  3. Wallet lưu Merchant’s Account Balance.
  4. PSP giữ Payment Execution Status.
  5. Data có thể được Replicated giữa Different Database Replicas để Improve Reliability.

Trong Distributed Environment, Communication giữa Any Two Services có thể Fail, dẫn đến Data Inconsistency. Hãy xem một số Techniques để Address Data Inconsistency trong Payment System.

Để Maintain Data Consistency giữa Internal Services, Ensuring Exactly-Once Processing rất quan trọng.

Để Keep Data Consistency giữa Internal Services và External Services (PSP), chúng ta thường dựa vào Idempotency và Reconciliation. Nếu External Service hỗ trợ Idempotency, chúng ta nên sử dụng Same Idempotency Key cho Payment Retry Operations. Ngay cả khi External Service hỗ trợ Idempotent APIs, Reconciliation vẫn cần thiết, vì chúng ta không nên Assume External Systems luôn Correct.

Nếu Data được Replicated, Replication Lag có thể dẫn đến Data Inconsistency giữa Master Database và Replicas. Thông thường có hai cách để Resolve Problem này:

  1. Chỉ Serve Reads và Writes từ Master Database. Phương pháp này Easy to Set Up, nhưng Obvious Drawback là Scalability. Replicas được dùng để Ensure Data Reliability, nhưng chúng không Serve Any Traffic, Wasting Resources.
  2. Ensure All Replicas Always Synchronized. Chúng ta có thể sử dụng Consensus Algorithms như Paxos [21] và Raft [22], hoặc Consensus-Based Distributed Databases, như YugabyteDB [23] hoặc CockroachDB [24].

Security

Payment Security rất quan trọng. Trong phần cuối của System Design này, chúng ta giới thiệu ngắn gọn một số Techniques để Combat Cyber Attacks và Credit Card Theft.

Issue Solution
Request/Response Eavesdropping Sử dụng HTTPS
Data Tampering Triển khai Encryption và Integrity Monitoring
Man-in-the-Middle Attacks Sử dụng SSL với Certificate Pinning
Data Loss Database Replication Across Multiple Regions và Data Snapshots
Distributed Denial-of-Service (DDoS) Attacks Rate Limiting và Firewalls [25]
Credit Card Theft Tokenization. Không sử dụng Real Card Numbers, thay vào đó lưu Tokens và dùng để Pay
PCI Compliance PCI DSS là Information Security Standard cho Organizations xử lý Branded Credit Cards
Fraud Address Verification, Card Verification Value (CVV), User Behavior Analysis, v.v. [26] [27]

Table 6: Payment Security

Step 4 – Wrap Up

Trong chương này, chúng ta đã nghiên cứu Payment Flow và Payout Flow. Chúng ta đi sâu vào Retry, Idempotency, và Consistency. Phần cuối của chương cũng đề cập đến Payment Error Handling và Security.

Payment Systems là Extremely Complex. Mặc dù đã thảo luận nhiều Topics, vẫn còn Many More Worth Mentioning. Dưới đây là Representative List của Related Topics, nhưng không Exhaustive:

  • Monitoring. Monitoring Key Metrics là Critical Part của Any Modern Application. Với Extensive Monitoring, chúng ta có thể trả lời câu hỏi như “Average Acceptance Rate của Specific Payment Method là bao nhiêu?” hoặc “CPU Usage của Servers là bao nhiêu?”. Chúng ta có thể Create và Display These Metrics trên Dashboard.
  • Alerting. Khi Anomalous Conditions xảy ra, Alerting On-Call Developers để họ React Promptly là quan trọng.
  • Debugging Tools. “Tại sao Payment Failed?” là Common Question. Để Make Debugging Easier cho Engineers và Customer Support Staff, Developing Tools cho phép Employees xem Transaction Status, Processing Server History, PSP Records, v.v. là Very Important.
  • Currency Conversion. Khi Designing Payment System cho International User Base, Currency Conversion là Important Consideration.
  • Geolocation. Different Regions có thể có Completely Different Payment Methods.
  • Cash Payments. Cash Payments rất Popular ở India, Brazil, và Some Other Countries. Uber [28] và Airbnb [29] đã viết Detailed Engineering Blogs về cách họ Handle Cash Payments.
  • Google/Apple Pay Integration. Nếu quan tâm, đọc [30].

Chúc mừng bạn đã đọc đến đây! Hãy tự thưởng cho mình nhé. Làm tốt lắm!

Chapter Summary

Chapter seated Summary

References

  1. Payment System
  2. Anti-Money Laundering/Anti-Terrorism Financing
  3. Card Scheme
  4. ISO 4217
  5. Stripe API Reference
  6. Double-Entry Bookkeeping
  7. Books, an Immutable Double-Entry Accounting Database Service
  8. Payment Card Industry Data Security Standard
  9. Tipalti
  10. Nonce
  11. Webhooks
  12. Customize Your Success Page
  13. 3D Secure
  14. Kafka Connect Deep Dive – Error Handling and Dead Letter Queues
  15. Reliable Processing in a Streaming Payment System
  16. Chain Services with Exactly-Once Guarantees
  17. Exponential Backoff
  18. Idempotency
  19. Stripe Idempotent Requests
  20. Idempotency
  21. Paxos
  22. Raft
  23. YugabyteDB
  24. CockroachDB
  25. What is a DDoS Attack?
  26. How Payment Gateways Detect and Prevent Online Fraud
  27. Uber’s Advanced Technologies for Detecting and Preventing Fraud
  28. Uber Engineering Re-Architects Cash and Digital Wallet Payments with India
  29. Scaling Airbnb’s Payment Platform
  30. Uber’s Payment Integration: A Case Study