Hệ thống thanh toán
Hiểu đề
Yêu cầu chức năng
- Merchant có thể tạo một yêu cầu thanh toán qua API và nhận trạng thái cuối cùng (thành công/thất bại)
- Khách có thể trả bằng thẻ, ví điện tử hoặc QR chuyển khoản ngân hàng
- Merchant nhận webhook mỗi khi trạng thái thanh toán thay đổi
- Merchant có thể hoàn tiền toàn phần hoặc một phần
- Merchant xem được lịch sử giao dịch và số dư chờ quyết toán
Yêu cầu phi chức năng
- Không trừ tiền hai lần: 0 giao dịch trùng do client hoặc hệ thống thử lại
- Sổ cái luôn cân (tổng nợ = tổng có), lệch với PSP được phát hiện trong tối đa 24 h
- API tạo thanh toán sẵn sàng 99,95%, p95 < 300 ms (chưa tính thời gian khách ở trang PSP)
- Webhook đến merchant p95 < 5 s, thử lại tối đa 3 ngày nếu merchant lỗi
- Không lưu số thẻ ở các service thường; giữ phạm vi PCI DSS nhỏ nhất có thể
Ngoài phạm vi: Đăng ký và KYC merchant; Quy đổi ngoại tệ, thanh toán xuyên biên giới; Quy trình xử lý khiếu nại/chargeback chi tiết; Chi tiết chuyển tiền quyết toán (payout) ra tài khoản merchant.
Thực thể và API
| Method | Endpoint | Ghi chú |
|---|---|---|
| POST | /v1/payments | Tạo thanh toán. Bắt buộc header Idempotency-Key. Trả payment_id và URL/QR để khách thanh toán. |
| GET | /v1/payments/{id} | Đọc trạng thái hiện tại. Merchant dùng để kiểm tra khi lỡ webhook. |
| POST | /v1/payments/{id}/refunds | Hoàn tiền toàn phần/một phần, cũng cần Idempotency-Key. |
| POST | /v1/providers/{psp}/webhook | PSP gọi về. Kiểm tra chữ ký, xử lý idempotent theo mã giao dịch PSP. |
| GET | /v1/balance?from=&to= | Số dư và bút toán của merchant, đọc từ replica. |
Thiết kế tổng thể
Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.
1) Merchant có thể tạo yêu cầu thanh toán
Bước tạo chỉ ghi nhận ý định thu tiền, chưa chạm tới tiền. Idempotency-Key được ghi cùng transaction với Payment nên gửi lại bao nhiêu lần cũng chỉ có một Payment.
- 1Server merchant gọi POST /v1/payments kèm Idempotency-Key và order_ref
- 2API xác thực API key, rate limit theo merchant
- 3Trong 1 transaction: INSERT idempotency_keys (UNIQUE merchant_id + key) và INSERT payments trạng thái CREATED
- 4Trả payment_id và URL trang thanh toán (hoặc QR); lần gửi lại cùng key nhận đúng response cũ
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97), mục "Writing idempotent message handlers" (tạo order với ID do client cấp là idempotent) ; Grokking Modern System Design · ch.19 Rate Limiter ↗ · bài "System Design: The Rate Limiter", mục "Why do we need a rate limiter?" ; Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
2) Khách có thể trả bằng thẻ, ví hoặc QR
Khách nhập thẻ trên trang/iframe của PSP nên số thẻ không đi qua server của mình. Kết quả về bất đồng bộ qua webhook của PSP, còn redirect của trình duyệt chỉ để hiển thị.
- 1API ghi PaymentAttempt với mã tham chiếu duy nhất TRƯỚC khi gọi PSP, Payment sang PROCESSING
- 2Khách nhập thẻ/OTP hoặc quét QR trên trang PSP, PSP trả token hoặc kết quả
- 3PSP gọi webhook: kiểm tra chữ ký, tìm attempt theo mã tham chiếu, bỏ qua nếu đã xử lý
- 4Trong 1 transaction: Payment sang SUCCEEDED, ghi JournalEntry (nợ phải thu PSP, có phải trả merchant, có doanh thu phí), ghi outbox
- 5Trình duyệt quay về trang merchant, merchant đọc trạng thái từ API hoặc chờ webhook
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100), mục "Using a database table as a message queue" (cập nhật DB và ghi outbox trong cùng transaction) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.2 Countermeasures for handling the lack of isolation (trạng thái *_PENDING, tr. 128–132) ; Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
3) Merchant nhận webhook khi trạng thái đổi
Gọi HTTP sang merchant có thể chậm hoặc lỗi nên không làm trong transaction thanh toán. Outbox đảm bảo sự kiện không mất, worker gửi lại với backoff.
- 1Outbox relay đọc bản ghi mới, đẩy vào queue
- 2Worker POST sang URL merchant, ký HMAC kèm event_id và timestamp
- 3Lỗi hoặc timeout thì thử lại theo backoff mũ (1 phút, 5 phút, 30 phút... đến 3 ngày), quá hạn vào dead-letter
- 4Merchant xử lý idempotent theo event_id, trả 2xx
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100) ; Building Microservices ↗ (.zip) · ch.4 Integration · mục "Complexities of Asynchronous Architectures" (giới hạn retry, dead letter queue, tr. 57–58) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97) ; Chưa có nguồn trong thư viện: chữ ký HMAC của webhook và lịch backoff mũ không có trong sách
4) Hoàn tiền và đối soát
Hoàn tiền là một Payment ngược chiều với bút toán đảo, không sửa bút toán cũ. Mỗi ngày so khớp sổ cái với file quyết toán của PSP để bắt mọi lệch.
- 1POST /refunds kiểm tra tổng đã hoàn ≤ số đã thu, ghi Refund PENDING và bút toán tạm giữ
- 2Gọi API hoàn tiền của PSP với mã tham chiếu duy nhất, xác nhận qua webhook
- 3Job đối soát tải file quyết toán của PSP, so từng dòng với PaymentAttempt và bút toán
- 4Dòng lệch (PSP có mà mình không, số tiền khác, trạng thái khác) vào hàng chờ xử lý ngoại lệ
Nguồn: Chưa có nguồn trong thư viện: sổ cái kép (double-entry) là kiến thức kế toán, không có trong sách của thư viện ; Microservices Patterns ↗ (.zip) · ch.4 · §4.1.3 Using the Saga pattern to maintain data consistency (bù trừ bằng thao tác ngược, tr. 114–117) ; Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
Hạ tầng theo quy mô
Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.
Cổng cho vài trăm shop nhỏ. Hai instance API và một Postgres có standby là đủ; tính đúng quan trọng hơn hiệu năng nên mọi thứ nằm trong transaction của Postgres.
Ước lượng
- Giao dịch: 20.000 / 86.400 ≈ 0,23 giao dịch/s, đỉnh ×10 ≈ 2,3 giao dịch/s
- API: mỗi giao dịch ≈ 5 request (tạo, xác nhận, webhook PSP, 2 lần hỏi trạng thái) → 2,3 × 5 ≈ 12 req/s đỉnh
- Instance: ceil(12 / 600) = 1 → chạy 2 để dự phòng
- Sổ cái: 20.000 × 4 posting × 200 B = 16 MB/ngày × 365 ≈ 5,8 GB/năm
- Payment + attempt + idempotency: 20.000 × 2 KB = 40 MB/ngày × 365 ≈ 14,6 GB/năm
- Webhook merchant: 20.000 × 2 sự kiện = 40.000/ngày ≈ 0,46/s, job table trong Postgres là đủ
Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · bài "Examples of Resource Estimation", mục "Number of servers required", "Storage requirements" ; Quy tắc ước lượng của khoá học · /hoc/chon-server (500 req/s mỗi vCPU, chạy 60% CPU, Redis ×1,3; số request mỗi giao dịch, đỉnh ×10 và kích thước bản ghi là giả định của khoá học)
Bảng hạ tầng
| Thành phần | Chọn | Số lượng | Cấu hình | Vì sao |
|---|---|---|---|---|
| API | Node.js (NestJS), Docker trên Cloud Run | 2 | 2 vCPU · 4 GB RAM | 12 req/s chỉ cần 1 instance, chạy 2 để không mất thanh toán khi một cái lỗi. |
| Database | PostgreSQL managed HA | 1 primary + 1 standby | 2 vCPU · 8 GB · 100 GB SSD | Khoảng 20 GB/năm là nhỏ, nhưng sổ cái cần ACID và standby đồng bộ vì mất dữ liệu là mất tiền. |
| Outbox + webhook | Bảng outbox + pg-boss chạy trong API | 1 (trong API) | Dùng chung instance API | 0,46 webhook/s, chưa cần queue riêng; outbox vẫn đảm bảo không mất sự kiện. |
| Đối soát | Cron (Cloud Scheduler) hằng ngày + mỗi 5 phút | 1 | Job ngắn trên Cloud Run | Hằng ngày so file PSP với sổ cái; mỗi 5 phút hỏi lại các attempt UNKNOWN. |
| PSP | VNPay / MoMo / Stripe, trang thanh toán hosted | 1–2 | Dịch vụ bên ngoài | Số thẻ không đi qua server mình, phạm vi PCI ở mức thấp nhất. |
| Bí mật | Secret Manager / KMS | 1 | Managed | Giữ khóa PSP và khóa ký webhook, không để trong biến môi trường thô. |
- Điểm xuất phát: một API, một Postgres HA, PSP lo phần thẻ
- Idempotency key, sổ cái kép và outbox có ngay từ ngày đầu vì sửa sau rất đắt
- Đối soát chạy bằng cron, lệch thì báo cho người xử lý
Khi lượng giao dịch tăng 100 lần, gửi webhook trong process API làm chậm API khi merchant phản hồi chậm, và một PSP duy nhất là điểm chết của cả hệ thống.
Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.
So sánh nhanh ba mức
| Thành phần | Mức 1 | Mức 2 | Mức 3 |
|---|---|---|---|
| Người dùng | 10.000 DAU · ≈ 20.000 giao dịch/ngày cho vài trăm shop | 1 triệu DAU · ≈ 2 triệu giao dịch/ngày | 50 triệu DAU · ≈ 50 triệu giao dịch/ngày (ví điện tử lớn) |
| Chi phí | ≈ 150–400 USD/tháng (ước lượng, chưa tính phí PSP) | ≈ 3.000–8.000 USD/tháng (ước lượng, chưa tính phí PSP) | ≈ 60.000–150.000 USD/tháng (ước lượng, chưa tính phí PSP) |
| API | Node.js (NestJS), Docker trên Cloud Run 2 × 2 vCPU · 4 GB RAM | Chưa cần | Chưa cần |
| Database | PostgreSQL managed HA 1 primary + 1 standby × 2 vCPU · 8 GB · 100 GB SSD | PostgreSQL HA (Aurora / Cloud SQL) 1 primary + 1 standby + 1 read replica × 8 vCPU · 32 GB · 1 TB SSD | Chưa cần |
| Outbox + webhook | Bảng outbox + pg-boss chạy trong API 1 (trong API) × Dùng chung instance API | Chưa cần | Chưa cần |
| Đối soát | Cron (Cloud Scheduler) hằng ngày + mỗi 5 phút 1 × Job ngắn trên Cloud Run | Job batch + worker quét UNKNOWN 1 × 2 vCPU · 4 GB RAM | Chưa cần |
| PSP | VNPay / MoMo / Stripe, trang thanh toán hosted 1–2 × Dịch vụ bên ngoài | 2–3 nhà cung cấp (Napas, VNPay, Stripe...) 2–3 × Dịch vụ bên ngoài, adapter riêng | Chưa cần |
| Bí mật | Secret Manager / KMS 1 × Managed | Chưa cần | Chưa cần |
| Gateway | Chưa cần | Cloud Load Balancer + WAF 1 × TLS, rate limit theo API key | Envoy / Kong + WAF, 3 AZ 6 pod × 4 vCPU · 8 GB mỗi pod |
| Payment API | Chưa cần | Node.js (NestJS), container autoscale 3–6 (autoscale) × 2 vCPU · 4 GB RAM | Node.js trên Kubernetes, 3 AZ 49 pod (HPA) × 2 vCPU · 4 GB mỗi pod |
| Redis | Chưa cần | ElastiCache / Memorystore 1 primary + 1 replica × 2 vCPU · 4 GB | Chưa cần |
| Queue | Chưa cần | Amazon SQS / Google Pub/Sub 1 × Có dead-letter queue | Chưa cần |
| Worker webhook | Chưa cần | Node.js consumer 2 × 1 vCPU · 2 GB RAM | Node.js consumer trên Kubernetes 24 pod × 1 vCPU · 2 GB mỗi pod |
| Chống gian lận | Chưa cần | Chưa cần | Service chấm điểm (luật + model) 10 pod × 4 vCPU · 8 GB mỗi pod |
| Vault thẻ (PCI) | Chưa cần | Chưa cần | Service riêng + KMS/HSM, VPC tách 3 pod × 2 vCPU · 4 GB mỗi pod |
| Sổ cái | Chưa cần | Chưa cần | PostgreSQL chia shard theo account_id (Citus hoặc shard ở tầng ứng dụng) 8 shard × (1 primary + 2 replica) × 16 vCPU · 64 GB · 4 TB NVMe |
| Redis Cluster | Chưa cần | Chưa cần | ElastiCache Redis Cluster 3 shard × (1 primary + 1 replica) × 4 vCPU · 16 GB mỗi node |
| Kafka | Chưa cần | Chưa cần | MSK / Confluent, 3 AZ 6 broker × Topic payments 24 partition, RF 3 |
| Đối soát + kho dữ liệu | Chưa cần | Chưa cần | Spark / BigQuery job, dữ liệu cũ trên S3 Cụm job theo lịch × Autoscale |
Đào sâu
Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.
1) Server merchant bị timeout và gửi lại POST /payments. Làm sao chỉ tạo một thanh toán?
Hai request gửi gần như cùng lúc đều đọc thấy chưa có, rồi cả hai cùng INSERT. Đây là lỗi check-then-act kinh điển.
Đánh đổi: Chạy đúng khi test tay, sai đúng lúc mạng chập chờn và client thử lại dồn dập.
Nguồn: Microservices Patterns ↗ (.zip) · ch.4 · §4.3.1 Overview of anomalies (hai tác vụ chạy song song cho kết quả sai, tr. 127–128) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96)
Request đầu SET NX thành công thì xử lý, request sau thấy key đã có thì trả kết quả lưu kèm. Nhanh và chặn được phần lớn trùng lặp.
Đánh đổi: Redis và DB là hai hệ riêng: server chết sau khi SET mà trước khi ghi DB thì key treo, Redis failover có thể mất key và để lọt request trùng.
Nguồn: Redis in Action ↗ · ch.6 · §6.2.3 Building a lock in Redis (SETNX, tr. 120–123) ; Redis in Action ↗ · ch.4 · §4.3 Handling system failures (tr. 75–78: dữ liệu Redis có thể mất khi failover)
INSERT key cùng transaction với Payment nên chỉ một request thắng, ràng buộc UNIQUE làm việc đó thay cho code. Request trùng key và trùng body nhận lại response cũ; trùng key nhưng khác body trả 422; request đầu chưa xong trả 409 để client thử lại sau. Đây là cách bảng PROCESSED_MESSAGE dùng để bỏ message trùng (Microservices Patterns §3.3.6). Redis SET NX chỉ đặt phía trước để lọc nhanh khi bị spam.
Đánh đổi: Thêm một lần ghi mỗi request và phải dọn key quá 24 h; đổi lại tính đúng không phụ thuộc Redis.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97), mục "Tracking messages and discarding duplicates" (bảng PROCESSED_MESSAGE, INSERT fail khi trùng, tr. 96–97) ; Grokking Modern System Design · ch.9 Databases ↗ · bài "Types of Databases", mục "Relational databases" › "Concurrency"
2) Lưu tiền và số dư thế nào để không bao giờ lệch và luôn kiểm toán được?
Đơn giản nhưng không có lịch sử: số dư sai thì không biết sai từ giao dịch nào. Đọc rồi ghi ở tầng ứng dụng còn dễ mất cập nhật khi chạy song song.
Đánh đổi: Không qua được bất kỳ đợt kiểm toán nào, sửa lỗi phải đoán.
Nguồn: Chưa có nguồn trong thư viện: sổ cái kép (double-entry) là kiến thức kế toán, không có trong sách của thư viện ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.1 Overview of anomalies (lost updates, tr. 127–128)
Mỗi biến động ghi một dòng transaction và cập nhật balance cùng lúc. Có lịch sử, không mất cập nhật.
Đánh đổi: Ghi sổ đơn: không chứng minh được tiền không tự sinh ra hay biến mất, và một merchant lớn khóa một dòng balance thành điểm nghẽn.
Nguồn: Grokking Modern System Design · ch.9 Databases ↗ · bài "Types of Databases", mục "Relational databases" › "Concurrency" ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.1 Overview of anomalies (lost updates)
Mỗi sự kiện tiền là một JournalEntry gồm nhiều Posting có tổng bằng 0, ví dụ khách trả 100.000đ: nợ 'phải thu PSP' 100.000, có 'phải trả merchant' 98.000, có 'doanh thu phí' 2.000. Không UPDATE hay DELETE; sai thì ghi bút toán đảo. Số tiền là số nguyên ở đơn vị nhỏ nhất, không dùng float. Số dư là tổng posting, có bảng snapshot số dư theo version để đọc nhanh. Kiểm tra tổng bằng 0 cho từng entry ngay trong transaction.
Đánh đổi: Nhiều dòng hơn và phải giữ snapshot số dư; tài khoản nóng (merchant rất lớn) cần chia thành nhiều tài khoản con rồi gộp định kỳ.
Nguồn: Chưa có nguồn trong thư viện: sổ cái kép (double-entry) là kiến thức kế toán, không có trong sách của thư viện ; Microservices Patterns ↗ (.zip) · ch.6 · §6.1.2 Overview of event sourcing và §6.1.5 Using snapshots to improve performance (chỉ ghi thêm event, snapshot để đọc nhanh, tr. 186–197)
3) Gọi PSP bị timeout. Tiền đã bị trừ hay chưa, và làm sao không trừ hai lần mà cũng không bỏ sót?
Timeout không có nghĩa là thất bại: PSP có thể đã trừ tiền nhưng phản hồi bị mất. Lần bấm lại tạo một charge mới với mã mới.
Đánh đổi: Khách bị trừ tiền hai lần, đội hỗ trợ phải hoàn tiền tay.
Nguồn: Building Microservices ↗ (.zip) · ch.5 Splitting the Monolith · mục "Transactional Boundaries" ("Try Again Later", "Abort the Entire Operation", tr. 89–92) ; Grokking Modern System Design · ch.3 Abstractions ↗ · bài "The Spectrum of Failure Models", mục "Omission failures"
Phần lớn PSP từ chối hoặc trả lại kết quả cũ nếu nhận trùng mã đơn/idempotency key. Thử lại cùng mã thì không thành charge mới, kết quả cuối lấy từ webhook.
Đánh đổi: Webhook có thể mất, đến muộn hoặc sai thứ tự; không có gì bắt các thanh toán treo ở trạng thái không rõ.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97) ; Building Microservices ↗ (.zip) · ch.11 Microservices at Scale · mục "Idempotency" (tr. 215) ; Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
Ghi PaymentAttempt với mã tham chiếu trước khi gọi PSP. Timeout thì chuyển UNKNOWN, tuyệt đối không tạo attempt mới với mã khác. Webhook đã kiểm chữ ký được xử lý idempotent theo mã giao dịch PSP và chỉ cho phép chuyển trạng thái hợp lệ (SUCCEEDED không quay về PROCESSING). Worker quét attempt UNKNOWN/PROCESSING quá 5 phút và gọi API tra cứu của PSP. Cuối ngày đối soát 3 bên: sổ cái, file quyết toán PSP, sao kê ngân hàng; tiền đã thu mà đơn đã hủy thì tự hoàn.
Đánh đổi: Một số thanh toán mất vài phút mới có trạng thái cuối, merchant phải xử lý trạng thái 'đang xử lý'; cần người vận hành hàng chờ ngoại lệ.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.2 Countermeasures for handling the lack of isolation (*_PENDING) ; Grokking Modern System Design · ch.23 Distributed Task Scheduler ↗ · bài "Design Considerations", mục "Task idempotency" ; Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
4) Có nên tự nhận và lưu số thẻ? Thu hẹp phạm vi PCI DSS thế nào?
Mọi service, log, backup và người có quyền truy cập DB đều nằm trong phạm vi PCI. Chỉ cần một dòng log in ra request body là lộ số thẻ.
Đánh đổi: Chi phí audit và vận hành rất lớn, rủi ro rò rỉ cao, không đáng với hầu hết công ty.
⚠ Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
Số thẻ đi thẳng từ trình duyệt sang PSP, server chỉ thấy token và 4 số cuối. Phạm vi PCI thu về mức tự đánh giá đơn giản nhất.
Đánh đổi: Token thuộc về PSP nên khó đổi hoặc chạy song song nhiều PSP; giao diện bị giới hạn theo PSP.
⚠ Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản
Khi cần định tuyến nhiều PSP hoặc kết nối thẳng ngân hàng, đặt một service vault nhỏ trong VPC riêng: nhận số thẻ, mã hóa envelope bằng KMS/HSM, trả token nội bộ. CVV không bao giờ lưu. Chỉ vault được detokenize để chuyển cho PSP. Payment API, sổ cái, log, analytics chỉ thấy token nên nằm ngoài phạm vi audit.
Đánh đổi: Vẫn phải audit vault và vận hành HSM; chỉ đáng khi quy mô đủ lớn để tiết kiệm phí nhờ định tuyến nhiều PSP.
⚠ Chưa có nguồn trong thư viện: hành vi của PSP/cổng thanh toán (webhook, mã tham chiếu, file quyết toán) và PCI DSS không có trong sách nào của thư viện; chỉ nêu khái niệm cơ bản ; Chưa có nguồn trong thư viện: vault thẻ, KMS/HSM và mã hóa envelope không có trong sách; không đưa số liệu cụ thể
Trong phỏng vấn
- “Tôi coi tính đúng là yêu cầu số một: mọi API ghi tiền đều cần Idempotency-Key lưu cùng transaction với dữ liệu.”
- “Tiền nằm trong sổ cái kép chỉ thêm, sai thì ghi bút toán đảo, số tiền là số nguyên.”
- “Timeout khi gọi PSP là trạng thái UNKNOWN chứ không phải thất bại; tôi hỏi lại PSP và đối soát hằng ngày.”
- “Số thẻ không đi qua server thường: dùng trang hosted của PSP, hoặc vault riêng khi quy mô đủ lớn.”
- Dùng float cho số tiền.
- Coi timeout là thất bại và tạo charge mới khi thử lại.
- Tin redirect của trình duyệt thay cho webhook đã kiểm chữ ký.
- Gửi webhook cho merchant ngay trong transaction thanh toán.
Đọc thêm
Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.