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 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 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:
- Khi User nhấn “Place Order” Button, Payment Event được tạo và gửi đến Payment Service.
- Payment Service lưu Payment Event trong Database.
- Đô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.
- Payment Executor lưu Payment Instructions trong Database.
- Payment Executor gọi External PSP để xử lý Credit Card Payment.
- 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.
- Wallet Server lưu Updated Balance Information trong Database.
- Sau khi Wallet Service cập nhật Seller Balance thành công, Payment Service gọi Ledger để Update.
- 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ì:
- Different Protocols, Software, và Hardware có thể hỗ trợ Different Numerical Precision trong Serialization và Deserialization, dẫn đến Unexpected Rounding Errors.
- 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:
- 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.
- Rich Tooling Support, như Monitoring và Investigation Tools.
- 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:
payment_order_status
khởi đầu làNOT_STARTED
.- Khi Payment Service gửi Payment Order đến Payment Executor,
payment_order_status
làEXECUTING
. - Payment Service cập nhật
payment_order_status
thànhSUCCESS
hoặcFAILED
dựa trên Payment Executor’s Response.
Khi payment_order_status
là SUCCESS
, 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.
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:
- 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.
- 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.
Để đơ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.
- User nhấn “Checkout” Button trong Client Browser. Client gọi Payment Service với Payment Order Information.
- 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.
- 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.
- Payment Service lưu Token trong Database trước khi gọi PSP’s Hosted Payment Page.
- 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.
- 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.
- PSP trả về Payment Status.
- 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
- 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 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:
- 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.
- 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.
- 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.
- 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).
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 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.
- 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.
- Payment System sử dụng Events trong Retry Queue và Retries Failed Payment Transactions.
- 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:
- Nó được Executed At Least Once.
- Đồ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.
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.
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:
- Khi Payment System nhận Payment, nó cố gắng Insert Row vào Database Table.
- Successful Insertion nghĩa là chúng ta chưa thấy Payment Request này trước đây.
- 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:
- Payment Service lưu Payment-Related Data, như Nonce, Token, Payment Orders, Execution Status, v.v.
- Ledger lưu All Accounting Data.
- Wallet lưu Merchant’s Account Balance.
- PSP giữ Payment Execution Status.
- 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:
- 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.
- 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
References
- Payment System
- Anti-Money Laundering/Anti-Terrorism Financing
- Card Scheme
- ISO 4217
- Stripe API Reference
- Double-Entry Bookkeeping
- Books, an Immutable Double-Entry Accounting Database Service
- Payment Card Industry Data Security Standard
- Tipalti
- Nonce
- Webhooks
- Customize Your Success Page
- 3D Secure
- Kafka Connect Deep Dive – Error Handling and Dead Letter Queues
- Reliable Processing in a Streaming Payment System
- Chain Services with Exactly-Once Guarantees
- Exponential Backoff
- Idempotency
- Stripe Idempotent Requests
- Idempotency
- Paxos
- Raft
- YugabyteDB
- CockroachDB
- What is a DDoS Attack?
- How Payment Gateways Detect and Prevent Online Fraud
- Uber’s Advanced Technologies for Detecting and Preventing Fraud
- Uber Engineering Re-Architects Cash and Digital Wallet Payments with India
- Scaling Airbnb’s Payment Platform
- Uber’s Payment Integration: A Case Study