returnUrl là nơi trình duyệt user quay về; IPN là cuộc gọi server-to-server từ cổng thanh toán. Chỉ IPN mới được dùng để chuyển đơn sang trạng thái đã thanh toán.
- returnUrl: user được redirect về sau khi trả tiền. Có thể không bao giờ xảy ra (đóng tab, mất mạng, app ví không mở lại trình duyệt), và tham số nằm trên URL nên không đáng tin. Chỉ dùng để hiển thị màn hình "đang xác nhận".
- IPN: cổng gọi thẳng vào endpoint public của bạn kèm chữ ký. Đây là nguồn sự thật.
Luồng tối thiểu:
1. Tạo đơn pending với mã tham chiếu duy nhất (vnp_TxnRef của VNPay, orderId của MoMo).
2. Ký tham số bằng secret rồi redirect user sang cổng (VNPay: HMAC-SHA512 vnp_SecureHash; MoMo: HMAC-SHA256 signature).
3. Nhận IPN thì tự tính lại chữ ký từ dữ liệu nhận được và so với chữ ký gửi tới; lệch thì bỏ qua.
4. Kiểm tra mã đơn có thật, số tiền khớp với đơn trong DB, đơn còn ở trạng thái pending.
5. Cập nhật đơn rồi trả đúng response cổng yêu cầu trong vài giây (VNPay: JSON RspCode; MoMo: HTTP 204). Trả sai hoặc chậm thì cổng sẽ gọi lại.
Hai lỗi bị hỏi vặn nhiều nhất: chỉ tin returnUrl nên user sửa query string thành "thành công"; và không so số tiền nên thanh toán 10.000đ vẫn mở đơn 10.000.000đ.