ADR-0003. Sự kiện thanh toán MQ-Pay đến qua webhook HTTP, không polling
| Trường | Giá trị |
|---|---|
| Trạng thái | Accepted |
| Ngày | 2026-02-15 |
| Người quyết định | sale-team, payment-team |
| Thay thế | — |
Bối cảnh
- Sau khi khách hàng hoàn tất thanh toán qua MQ-Pay (trừu tượng hóa VNPay v.v.), sale cần cập nhật trạng thái order, allocation usage, customer points, và phát Kafka cho service downstream.
- Hai cách để sale biết kết quả thanh toán:
- Poll MQ-Pay theo chu kỳ.
- Webhook — MQ-Pay POST tới sale.
- Polling lãng phí và độ trễ cao. UX POS thời gian thực cần cập nhật trạng thái tức thì.
Quyết định
MQ-Pay POST tới POST /v1/api/sale/webhooks/payment với 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, và phát KafkaTopics.PAYMENT_SUCCESS post-commit.
Sale không poll MQ-Pay. Webhook là tín hiệu thanh toán inbound duy nhất.
Endpoint webhook không có authentication. Trust được áp đặt bằng Cilium network policy: chỉ MQ-Pay mới có thể tới /webhooks/payment qua mạng nội bộ.
Hệ quả
| Ưu | Nhược |
|---|---|
| Trạng thái order thời gian thực (dưới giây sau thanh toán) | Single point of failure — nếu webhook không tới được sale, trạng thái bị kẹt |
| Không polling lãng phí | Bảo mật webhook phụ thuộc network policy, không phải auth header |
| MQ-Pay tự động retry 5xx | Sale phải idempotent dưới retry (handler short-circuit nếu đã ở trạng thái target) |
| Tách sale khỏi quirks của payment provider | Trust boundary ngầm; operator mới phải hiểu nó |
Phục hồi
- Nếu webhook fail liên tục, MQ-Pay đưa sự kiện vào queue và retry.
- Operator có thể manually re-trigger redelivery qua admin MQ-Pay.
- Idempotency của sale đảm bảo re-deliveries an toàn.
Phương án thay thế đã cân nhắc
| Phương án | Ưu | Nhược | Lý do từ chối |
|---|---|---|---|
| Poll MQ-Pay mỗi N giây | Pull model; sale kiểm soát thời điểm | Lãng phí; độ trễ cao; không scale tới hàng nghìn order | UX quá chậm cho POS |
| Hybrid: webhook + safety-net poll cho order kẹt | Resilient | Thêm phức tạp; rủi ro double-event | Có thể thêm sau nếu webhook chứng tỏ không đáng tin |
| Webhook với auth shared-secret | Bảo mật mạnh hơn | Chi phí vận hành xoay vòng secret; MQ-Pay đã ở mạng tin cậy | Network policy đủ với mô hình đe dọa 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(dispatcher)sale/src/services/sale-order-payment-webhook.service.ts(event handlers)