Developer

# Instant Payment Notification (IPN)

Pay2S gửi POST JSON từ server tới ipnUrl của đơn hàng khi đơn hoàn tất hoặc bị hủy. IPN không phụ thuộc khách còn mở trang thanh toán. Đây là luồng khác với webhook biến động số dư và URL quay lại trên trình duyệt.

Payload hiện tại

Payload đầy đủ đối chiếu worker ngày 11/10/2026 nằm ở cuối trang.

Trường Ý nghĩa
accessKey, partnerCode Khóa công khai và mã đối tác của bộ khóa tạo đơn
orderId Mã đơn merchant truyền khi tạo thanh toán
requestId ID hóa đơn nội bộ Pay2S, không phải requestId gốc của merchant
requestTrace requestId merchant đã gửi lúc tạo đơn
amount Số tiền đã trả khi thành công; số tiền đơn khi hủy
orderInfo Nội dung đơn
orderType, payType Loại thanh toán của đơn
extraData Worker hiện gửi chuỗi rỗng; không trả nguyên metadata V2
message Paymentsuccessful hoặc Paymentcancelled
resultCode 0: đã thanh toán; 2: hủy đơn
transId Mã hóa đơn Pay2S, dạng chuỗi
responseTime Unix timestamp mili giây; URL quay lại dùng giây
m2signature HMAC-SHA256 dạng hex

requestId, amount có thể là số hoặc chuỗi số tùy backend/driver. Giữ nguyên giá trị khi tạo chuỗi ký; không thêm dấu phân cách hàng nghìn.

Xác minh chữ ký

Dùng Secret Key của đúng bộ khóa đã tạo đơn (tài khoản chính hoặc cửa hàng). Ghép đúng thứ tự, không URL-encode và không thêm xuống dòng:

accessKey={accessKey}&amount={amount}&extraData={extraData}&message={message}&orderId={orderId}&orderInfo={orderInfo}&orderType={orderType}&partnerCode={partnerCode}&payType={payType}&requestId={requestId}&responseTime={responseTime}&resultCode={resultCode}&transId={transId}

m2signature = HMAC-SHA256(secretKey, chuỗi_trên). Không đưa requestTrace hoặc m2signature vào chuỗi ký. Chữ ký IPN khác chữ ký tạo đơn V1/V2 và khác chữ ký URL quay lại.

Xử lý và ACK

  1. Xác minh chữ ký, Partner Code và Access Key.
  2. Tìm đơn theo orderId, đối chiếu số tiền, chống xử lý trùng trong transaction của database.
  3. resultCode=0: cập nhật thanh toán đúng một lần. resultCode=2: xử lý hủy; không ghi nhận doanh thu hoặc hạ trạng thái đơn đã xác nhận thanh toán.
  4. Lưu kết quả bền vững rồi trả HTTP 200 và JSON bên dưới. Nếu cùng kết quả đã được xử lý thì vẫn ACK thành công.
{ "success": true }

Worker dùng timeout 10 giây. Khi thất bại, lần retry đầu lên lịch sau 5 giây, tiếp theo 15, 60, 300 giây; dừng khi bộ đếm lỗi đạt 5. Thời điểm thực nhận phụ thuộc worker và mạng. Không trả HTML hoặc chỉ 200 với body rỗng. ACK cũ chứa thông điệp Received payment result success vẫn được chấp nhận; tích hợp mới dùng success: true.

Xác minh bằng PHP

<?php
function verifyPay2SIpn(array $body, string $secret): bool {
    $fields = ['accessKey','amount','extraData','message','orderId','orderInfo',
      'orderType','partnerCode','payType','requestId','responseTime','resultCode','transId'];
    if ($secret === '') return false;
    $parts = [];
    foreach ($fields as $key) {
        if (!array_key_exists($key, $body) || !is_scalar($body[$key])) return false;
        $parts[] = $key . '=' . $body[$key];
    }
    $signature = $body['m2signature'] ?? null;
    return is_string($signature) && hash_equals(
      hash_hmac('sha256', implode('&', $parts), $secret), $signature
    );
}
// Gọi với JSON body và Secret Key lấy từ biến môi trường backend.
// Sau xác minh: đối chiếu Partner Code/Access Key/đơn/số tiền, lưu DB chống trùng,
// rồi trả HTTP 200, Content-Type application/json, body {"success":true}.

Xác minh bằng Node.js

import crypto from 'node:crypto';
export function verifyPay2SIpn(body, secretKey) {
  const fields = ['accessKey','amount','extraData','message','orderId','orderInfo',
    'orderType','partnerCode','payType','requestId','responseTime','resultCode','transId'];
  if (!secretKey || !body || fields.some(k => !['string','number'].includes(typeof body[k]))) return false;
  if (!/^[a-f0-9]{64}$/.test(body.m2signature || '')) return false;
  const raw = fields.map(k => `${k}=${body[k]}`).join('&');
  const expected = crypto.createHmac('sha256', secretKey).update(raw).digest();
  return crypto.timingSafeEqual(expected, Buffer.from(body.m2signature, 'hex'));
}
// Sau xác minh: đối chiếu Partner Code/Access Key/đơn/số tiền, lưu DB chống trùng,
// rồi res.status(200).json({success:true}).

Các hàm trên chỉ xác minh chữ ký. Cần nối nghiệp vụ và database của ứng dụng trước khi ACK; không log Secret Key hoặc nguyên thông tin xác thực.

Request và response đối chiếu ngày 11/10/2026

Các mẫu dưới đây giữ cấu trúc trường và kiểu dữ liệu. Khóa, chữ ký, URL phiên đã được thay bằng placeholder; danh sách chỉ giữ tối đa hai phần tử và dữ liệu ảnh/PDF/XML dài được rút gọn. Không sao chép placeholder để gọi API thật.

IPN · Gửi, nhận & xác minh chữ ký

Đối chiếu mã nguồn backend; giá trị minh họa. Payload từ sendIPN.js. Hủy đơn: resultCode=2, message=Paymentcancelled. requestTrace là requestId lúc tạo đơn; requestId là ID hóa đơn nội bộ.

POST {ipnUrl}

Body:

{
  "accessKey": "YOUR_ACCESS_KEY",
  "amount": 250000,
  "extraData": "",
  "message": "Paymentsuccessful",
  "orderId": "ORDER_DEMO_001",
  "orderInfo": "THANHTOANDEMO",
  "orderType": "pay2s",
  "partnerCode": "YOUR_PARTNER_CODE",
  "payType": "pay2s",
  "requestId": 10001,
  "requestTrace": "REQUEST_DEMO_001",
  "responseTime": 1791691200000,
  "resultCode": 0,
  "transId": "PAY2S_DEMO_INVOICE",
  "m2signature": "<HMAC_SHA256_HEX>"
}

HTTP 200. Response:

{
  "success": true
}
Tìm trong 57 trang tài liệu · Esc để đóng