ADR-004 — Cơ chế idempotency cho webhook thanh toán + job đối soát bù định kỳ
Tên file: adr/ADR-004_idempotency-doi-soat-webhook-thanh-toan.md
|
|
| Status |
Proposed |
| Date |
2026-09-15 |
| Người quyết |
SA + Tech Lead (tích hợp/idempotency — decision-radar.md §5) |
| Người đề xuất |
SA (qua skill sa-2-architecture, hoạt động adr) |
| Điểm radar |
~6/10 |
| Supersedes |
— |
| Superseded by |
— |
| Liên quan |
ASR-004 · QAS-014 · BR-CART-06 (BA) · DRV-08 · ARISK-03 · CMP-11 |
⚠️ ADR bất biến sau khi Accepted. Muốn đổi quyết định thì viết ADR mới có
Supersedes: ADR-004 và đổi trạng thái bản cũ thành Superseded by.
1. Bối cảnh
Xác nhận thanh toán qua webhook VNPay/Momo (bên thứ ba) chưa được xác minh khả thi gần thời gian
thực (DRV-08, ASM-01 của BA chưa xác minh). Nếu webhook trễ/lỗi/gọi lặp, đơn hàng có thể kẹt
"chờ thanh toán" dù tiền đã thu, hoặc bị cập nhật trạng thái sai nếu xử lý webhook không
idempotent. BR-CART-06 (BA) đã đặt quy tắc nghiệp vụ: chỉ cập nhật Payment.status = success
khi webhook có chữ ký hợp lệ và trong thời hạn — nhưng con số thời hạn cụ thể chưa chốt
(thuộc phạm vi kỹ thuật của ADR này, không phải quyết định BA).
Ràng buộc đang chi phối:
| Nguồn |
Nội dung |
ASR-004 |
Webhook phải idempotent + có job đối soát bù |
BR-CART-06 (BA) |
Chỉ cập nhật Payment.status=success khi chữ ký hợp lệ + trong hạn |
QAS-014 |
Job đối soát phát hiện lệch trong ≤15 phút (đề xuất SA, OQ-021 chưa xác nhận) |
ARISK-03 |
VNPay/Momo chưa có sandbox thật — thiết kế chưa kiểm chứng |
Cái đã biết chắc / cái còn là giả định:
| Điều |
🟢 Đã kiểm chứng / 🔴 Giả định |
Bằng chứng |
| Webhook có thể bị gọi lặp lại (retry của VNPay/Momo theo tài liệu công khai) |
🟡 Ước lượng có cơ sở |
Thực hành phổ biến của cổng thanh toán, chưa xác nhận với sandbox thật |
gateway_transaction_ref là khoá duy nhất, ổn định qua các lần gọi lặp |
🔴 Giả định |
Chưa có tài liệu API/sandbox thật (CON-04) |
| Tần suất đối soát ≤15 phút đủ nhanh để tránh khiếu nại CSKH |
🔴 Giả định (ASM-18) |
OQ-021 — Tech Lead chưa xác nhận |
2. Phương án đã cân nhắc
PA-1 — Chỉ dựa vào webhook, không có job đối soát bù
|
|
| Mô tả |
Cập nhật Payment.status hoàn toàn dựa vào webhook đến; không có cơ chế nào chủ động kiểm tra lại |
| Ưu |
Đơn giản nhất, không cần job chạy định kỳ |
| Nhược |
Nếu webhook mất/trễ vĩnh viễn (lỗi mạng, đối tác không gửi lại), đơn hàng kẹt "chờ thanh toán" vô thời hạn dù tiền đã thu — vi phạm trực tiếp DRV-08/ASR-004 |
| Chi phí đảo ngược |
Cao — phải xây job đối soát sau khi đã có dữ liệu thật bị kẹt, kèm backfill thủ công cho các đơn đã kẹt |
PA-2 — Webhook idempotent (khoá theo gateway_transaction_ref) + job đối soát bù định kỳ (chọn)
|
|
| Mô tả |
Webhook endpoint kiểm tra idempotency key = gateway_transaction_ref trước khi ghi; nếu đã xử lý, trả 200 mà không ghi lại. Job đối soát chạy định kỳ (đề xuất ≤15 phút, OQ-021), tra cứu API đối tác để so khớp Payment.status |
| Ưu |
Chống hiệu ứng phụ khi webhook gọi lặp; phát hiện được các trường hợp webhook mất hoàn toàn (job chủ động tra cứu, không phụ thuộc webhook đến) |
| Nhược |
Thêm một job chạy định kỳ cần giám sát riêng (alerting nếu job tự nó lỗi); cần bảng log webhook đã nhận để so khớp |
| Chi phí đảo ngược |
Trung bình — đổi tần suất/cơ chế đối soát sau khi có dữ liệu thật tốn công viết lại job + backfill, nhưng không ảnh hưởng mô hình dữ liệu cốt lõi |
PA-3 — Chỉ dùng job polling, không nhận webhook
|
|
| Mô tả |
Bỏ hẳn endpoint webhook, chỉ dựa vào job định kỳ tra cứu API đối tác để cập nhật trạng thái |
| Ưu |
Không cần lo idempotency cho webhook đến (không có webhook) |
| Nhược |
Độ trễ xác nhận thanh toán phụ thuộc hoàn toàn chu kỳ polling (tối thiểu bằng tần suất job) — tệ hơn nhiều so với gần thời gian thực; tăng số lượng API call tới đối tác (chi phí + rủi ro rate limit) |
| Chi phí đảo ngược |
Trung bình — thêm lại webhook sau này không khó, nhưng đã đánh đổi trải nghiệm người dùng trong thời gian dùng PA-3 |
Bảng so sánh
| Tiêu chí |
PA-1 |
PA-2 |
PA-3 |
Đáp ứng ASR-004 (idempotent + đối soát bù) |
❌ |
✅ |
🔶 Có đối soát, không idempotent webhook (không có webhook) |
Độ trễ xác nhận thanh toán (QAS-002) |
Nhanh nhất khi webhook tới đúng hạn, nhưng vô hạn khi mất |
Nhanh (webhook) + có lưới an toàn (job) |
Chậm nhất (phụ thuộc chu kỳ polling) |
| Chi phí vận hành thêm |
Thấp nhất |
Trung bình (1 job định kỳ) |
Trung bình-cao (nhiều API call hơn) |
3. Quyết định
Chọn PA-2 — Webhook idempotent (khoá gateway_transaction_ref) + job đối soát bù định kỳ.
Vì sao: Đây là phương án duy nhất vừa giữ được độ trễ thấp cho trường hợp bình thường (webhook
tới đúng hạn) vừa có lưới an toàn khi webhook trễ/mất — đúng yêu cầu ASR-004.
Phạm vi áp dụng: Toàn bộ luồng xác nhận thanh toán VNPay/Momo (không áp dụng cho COD — không
có webhook).
Điều kiện chuyển Proposed → Accepted
| Điều kiện |
Ai xác nhận |
Trạng thái hiện tại |
| Có sandbox VNPay/Momo thật để kiểm chứng hành vi retry webhook |
Tech Lead |
Chưa có (ARISK-03, CON-04) |
OQ-021 — Tech Lead xác nhận tần suất job đối soát cụ thể |
Tech Lead |
Mở (đề xuất tạm ≤15 phút) |
Không bắt buộc POC theo decision-radar.md §6 (không phải con số hiệu năng lớn, nhưng cần sandbox thật để xác minh hành vi đối tác) |
— |
Chờ sandbox |
4. Phương án bị loại và lý do
| Phương án |
Loại vì |
Gắn với |
Điều kiện nào thì xét lại |
| PA-1 — Chỉ dựa vào webhook, không đối soát |
Không phát hiện được trường hợp webhook mất hoàn toàn — vi phạm ASR-004 |
ASR-004, DRV-08 |
Không xét lại trừ khi VNPay/Momo đảm bảo bằng SLA hợp đồng webhook không bao giờ mất — chưa có tiền lệ ngành |
| PA-3 — Chỉ polling, không webhook |
Độ trễ xác nhận kém hơn hẳn, tăng API call — vi phạm gián tiếp QAS-002 (trải nghiệm checkout) |
QAS-002, QAS-014 |
Nếu VNPay/Momo không hỗ trợ webhook (một số cổng thanh toán khác trong tương lai) — xét lại cho riêng đối tác đó |
5. Hệ quả
Hệ quả tích cực
- Chống được hiệu ứng phụ khi webhook gọi lặp (idempotency key)
- Có lưới an toàn phát hiện đơn kẹt trong thời gian giới hạn (đề xuất ≤15 phút), giảm rủi ro
khiếu nại CSKH (
DRV-08)
Hệ quả tiêu cực phải sống chung
- Cần bảng log webhook đã nhận (
ReconciliationLog) — tăng dung lượng lưu trữ, cần retention
policy (thuộc DAT, hoạt động 5)
- Job đối soát tự nó có thể lỗi — cần alerting riêng cho chính job này (không chỉ alerting cho
luồng chính)
Cái quyết định này khoá lại
| Muốn đổi về sau thì |
Tốn |
Đổi khoá idempotency từ gateway_transaction_ref sang cơ chế khác |
Cần migrate log webhook đã có + rà lại toàn bộ giao dịch lịch sử |
Việc phát sinh
| Việc |
Chủ |
Hạn |
Ghi ở đâu |
| Đàm phán sandbox VNPay/Momo |
Tech Lead + PO |
Trước GĐ2 ICD hoàn tất |
ARISK-03 |
Thiết kế bảng ReconciliationLog + retention |
SA (hoạt động dat) |
GĐ2 tiếp theo |
DAT_e-commerce |
| Thêm alerting cho chính job đối soát (job lỗi/không chạy) |
SA (hoạt động inf) |
GĐ2 tiếp theo |
INF_e-commerce |
6. Cách kiểm chứng quyết định này được tuân thủ
| Cách kiểm |
Công cụ |
Chạy ở đâu |
FIT |
| Gọi lặp cùng một webhook payload không tạo hiệu ứng phụ (không nhân đôi trạng thái/log) |
Integration test |
CI + staging trước mỗi release |
FIT-07 (ứng viên) |
| Job đối soát phát hiện lệch giả lập trong ≤15 phút |
Test giả lập webhook trễ/mất trên staging |
Trước go-live |
— (cần sandbox thật, ARISK-03) |
7. Điều kiện xét lại
| Dấu hiệu |
Ngưỡng |
Ai theo dõi |
Tần suất job đối soát thật khác đề xuất (OQ-021 trả lời khác ≤15 phút) |
Khi Tech Lead xác nhận số khác |
Tech Lead |
Sandbox thật cho thấy webhook có hành vi khác giả định (VD không gọi lặp, hoặc gọi lặp với gateway_transaction_ref khác nhau) |
Khi có sandbox |
Tech Lead |
8. Tham chiếu
- POC: Không bắt buộc — nhưng cần sandbox thật (
ARISK-03) trước khi kiểm chứng đầy đủ
- Bài đo:
FIT-07 (ứng viên GĐ3)
- Tài liệu ngoài:
BR_CartCheckout_v1.0.md §1 BR-CART-06 (BA)
- Thảo luận:
ASR_e-commerce_v1.0.md §B2 ASR-004
9. Review log (không đổi Status)
| Ngày |
Người review |
Vai trò |
Quyết định |
Lý do chưa chuyển Accepted |
| 2026-09-15 |
Điều phối dự án (thay mặt Tech Lead, chế độ chạy thử) |
Tech Lead (ký thay, ngoại lệ DEC-01) |
Reviewed (Proposed giữ nguyên) — nội dung đủ làm cơ sở thiết kế tiếp (icd/dat/sec/inf/fail) |
Chưa có chữ ký thật Tech Lead; cần chạy thử trên sandbox đối tác thanh toán trước khi chốt |
Đây là ghi nhận review nội dung, không phải "sign" theo nghĩa gate: Status giữ nguyên
Proposed. Xem 00-index/ADL_e-commerce.md (Change Log — Review 2026-09-15) và ADL §8
"Việc phải làm" cho điều kiện chuyển Accepted. Confidence tổng thể của lượt review: 🔴 —
AG2 chưa ký.