ADR-0003. Sự kiện thanh toán MQ-Pay đến qua HTTP webhook, không hỏi vòng
| Trường | Giá trị |
|---|---|
| Trạng thái | Accepted |
| Ngày | 2026-02-15 |
| Người quyết định | Phat Nguyen |
| Thay thế cho | - |
Bối cảnh
- Sau khi khách hoàn tất thanh toán qua MQ-Pay (lớp trừu tượng cho VNPay và các cổng khác), sale cần cập nhật trạng thái đơn, allocation usage, điểm khách hàng, và phát Kafka cho các service phía sau.
- Có hai cách để sale biết được kết quả thanh toán:
- Hỏi vòng MQ-Pay theo chu kỳ.
- Webhook - MQ-Pay gửi POST tới sale.
- Hỏi vòng vừa lãng phí vừa độ trễ cao. Trải nghiệm POS thời gian thực cần cập nhật trạng thái tức thì.
Quyết định
MQ-Pay gửi POST tới POST /v1/api/sale/webhooks/payment với các loại sự kiện mq-pay:attempt.{success,failed,expired,cancelled} và mq-pay:transaction.{settled,cancelled}. Sale xử lý sự kiện, chuyển trạng thái, rồi phát KafkaTopics.PAYMENT_SUCCESS sau khi commit.
Sale không hỏi vòng MQ-Pay. Webhook là tín hiệu thanh toán đầu vào duy nhất.
Endpoint webhook không có xác thực. Độ tin cậy được bảo đảm bằng Cilium network policy: chỉ MQ-Pay mới truy cập được /webhooks/payment qua mạng nội bộ.
Hệ quả
| Ưu | Nhược |
|---|---|
| Trạng thái đơn theo thời gian thực (dưới một giây sau khi trả tiền) | Điểm hỏng đơn lẻ - nếu webhook không tới được sale, trạng thái sẽ kẹt |
| Không hỏi vòng lãng phí | Bảo mật webhook dựa vào network policy, không phải header xác thực |
| MQ-Pay tự động thử lại với lỗi 5xx | Sale phải idempotent khi bị thử lại (handler dừng sớm nếu đã ở trạng thái đích) |
| Tách sale khỏi các đặc thù riêng của nhà cung cấp thanh toán | Ranh giới tin cậy mang tính ngầm định; người vận hành mới phải hiểu điều này |
Khôi phục
- Nếu webhook hỏng liên tục, MQ-Pay đưa sự kiện vào queue và thử lại.
- Người vận hành có thể tự tay kích hoạt gửi lại qua trang admin của MQ-Pay.
- Nhờ tính idempotent của sale, việc gửi lại là an toàn.
Các phương án đã cân nhắc
| Phương án | Ưu | Nhược | Lý do từ chối |
|---|---|---|---|
| Hỏi vòng MQ-Pay mỗi N giây | Mô hình kéo; sale tự kiểm soát thời điểm | Lãng phí; độ trễ cao; không mở rộng tới hàng nghìn đơn | Trải nghiệm POS quá chậm |
| Kết hợp: webhook + hỏi vòng dự phòng cho đơn bị kẹt | Bền bỉ | Thêm phức tạp; rủi ro nhận sự kiện hai lần | Có thể bổ sung sau nếu webhook tỏ ra không đáng tin |
| Webhook kèm xác thực bằng khoá bí mật chung | Bảo mật mạnh hơn | Tốn công vận hành để xoay vòng khoá bí mật; MQ-Pay vốn đã ở mạng tin cậy | Network policy là đủ với mức độ đe doạ hiện tại |
Tham chiếu
sale/src/common/webhook-types.ts:9(PaymentWebhookEventTypes)sale/src/controllers/payment-webhook/payment-webhook.controller.tssale/src/services/payment-webhook.service.ts(bộ điều phối)sale/src/services/sale-order-payment-webhook.service.ts(các handler sự kiện)