Skip to content

Hợp đồng IPN VNPAY

Chuẩn & kiểm chứng theo code

Nguồn-sự-thật-duy-nhất cho hợp đồng thanh toán BANA ↔ VNPAY, kiểm chứng theo third-parties/mq-paypackages/{payment,sale}. Nếu các trang integrations/mq-pay hay trang khác mâu thuẫn, trang này thắng.

mq-pay không phải service riêng - nó là component (MQPayComponent) bên trong service payment. Production chỉ wire QR-MMSPhonePOS; Smart POS có trong code nhưng CHƯA provision (xem Vấn đề đã biết).

Luồng - hai chặng

  1. VNPAY → service payment. VNPAY POST IPN tới endpoint của provider; mq-pay verify checksum, đẩy job confirmation, và trả ack { code, message } (HTTP 200).
  2. service payment → sale. Khi có event kết thúc, webhook dispatcher POST { eventType, timestamp, payload } tới /webhooks/payment của sale.

Chặng 1 - VNPAY → payment (endpoint IPN)

Đường dẫn tuyệt đối = APP_ENV_SERVER_BASE_PATH (quy ước /v1/api/payment) + path tương đối bên dưới. Đây là URL đăng ký trên cổng merchant VNPAY.

ProviderEndpoint IPNOutbound base (TEST / PROD)Trạng thái
QR-MMSPOST /v1/api/payment/payments/vnpay/qr-mms/ipndoitac-tran.vnpaytest.vn / doitac-tran.vnpay.vn✅ wired
PhonePOSPOST /v1/api/payment/payments/vnpay/phone-pos/ipn- (SDK khởi tạo; chỉ nhận IPN)✅ wired
Smart POSPOST /v1/api/payment/payments/vnpay/smart-pos/ipnspos-api.vnpaytest.vn / spos-api.vnpay.vn⚠️ chưa provision

Chữ ký (checksum) từng provider

Checksum đi trong field body checksum (không phải header), thuật toán khác nhau theo provider:

ProviderThuật toánNguồn secretVerify IPN vào?
QR-MMSMD5 - nối secret vào chuỗi data pipe-join (không phải HMAC thật)per-merchant (credential getter)✅ có
PhonePOSHMAC-SHA256, Base64per-merchant (credential getter)✅ có - thiếu checksum bị từ chối
Smart POSHMAC-SHA256, hex (in hoa)static config (secretKey.ipn)⚠️ yếu - chấp nhận khi thiếu checksum (stub test)

QR-MMS chuỗi ký IPN (verify): code | msgType | txnId | qrTrace | bankCode | mobile | accountNo | amount | payDate | merchantCode | secretKey → MD5 hex, so sánh không phân biệt hoa thường. PhonePOS chuỗi ký IPN: secret + (merchantMethodCode | orderCode | amount | "" | responseCode) → HMAC-SHA256 Base64.

Ack trả về VNPAY

Không phải { RspCode, Message } cổ điển. mq-pay luôn trả HTTP 200 với { code, message, data? }:

  • QR-MMS thành công: { "code": "00", "message": "Success", "data": { "txnId": "…" } } - auth-fail 06, đã thanh toán 03, sai amount 07.
  • PhonePOS / Smart POS thành công: { "code": "200", "message": "…" } - sai checksum 410, đã xử lý 412.

Chặng 2 - payment → sale (webhook nội bộ)

  • Endpoint: POST /webhooks/payment (service sale).
  • Body: { eventType: string, timestamp: number, payload: { transaction?, attempt?, source?, timestamp } }.
    • transaction = { id, uid, status, total, paid, sourceType?, sourceId?, metadata? }
    • attempt = { id, uid, status, amount, paymentProvider, reason?, metadata? }
  • Response: HTTP 200 { success: true, message? }; lỗi validate schema trả 400 { error, code? }.
  • Chỉ event kết thúc mới được dispatch; TRANSACTION_CREATED / ATTEMPT_CREATED / ATTEMPT_SENT chỉ qua WebSocket.
  • Xác thực: hiện chưa có. Core có sẵn helper X-Webhook-Signature HMAC-SHA256 và dispatcher có thể đính kèm, nhưng sale không verify - xem Vấn đề đã biết.

Status code - giá trị thật

Từ MQPay*Statuses (alias Statuses của IGNIS). Band tối đa là 5xx - không có 600.

MiềnMember = giá trị
Transaction statusNEW 100_NEW · PARTIAL 300_PARTIAL · SETTLED 304_SETTLED · BLOCKED 403_BLOCKED · CLOSED 404_CLOSED · CANCELLED 505_CANCELLED
Attempt statusNEW 100_NEW · SENT 204_SENT · SUCCESS 302_SUCCESS · FAIL 500_FAIL · EXPIRED 501_EXPIRED
Attempt typeMAKE_PAYMENT 100_MAKE_PAYMENT · CANCEL_PAYMENT 200_CANCEL_PAYMENT · REFUND_PAYMENT 300_REFUND_PAYMENT

Lỗi doc thường gặp (đã sửa)

300_SUCCESS sai - 300_PARTIAL; thành công là 302_SUCCESS. SETTLED 600 sai - SETTLED là 304_SETTLED. Lấy giá trị từ bảng trên.

Vấn đề đã biết

  • Connector Smart POS chết trong service payment (không bao giờ được truyền vào IMQPayOptions); verify IPN trả hợp lệ khi thiếu checksum, thứ tự field ký chưa xác nhận. Đừng dựa vào nó.
  • /webhooks/payment chưa xác thực ở phía sale - tin header X-Webhook-Event-Type và có thể hoàn tất đơn mà không verify X-Webhook-Signature của dispatcher. Việc siết (verify HMAC + cửa sổ chống replay) là một mục Security.

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.