Appearance
Instant Payment Notification (IPN)
Giới thiệu
Instant Payment Notification (IPN) là cơ chế thông báo giao dịch tức thì từ hệ thống Pay2S đến máy chủ của merchant ngay khi có thay đổi trạng thái thanh toán, ví dụ như: giao dịch thành công, thất bại hoặc bị hủy.
Thông báo IPN được gửi tự động dưới dạng HTTP POST đến một địa chỉ URL do merchant cấu hình trong hệ thống Pay2S.
📌 Mục đích của IPN
- Giúp merchant nhận biết tức thời trạng thái giao dịch.
- Cho phép merchant xử lý tự động các hành động sau thanh toán như kích hoạt dịch vụ, gửi hàng, cập nhật trạng thái đơn hàng, v.v.
- Đảm bảo tính tự động, chính xác, và nhanh chóng trong quy trình vận hành.
⚙️ Cơ chế hoạt động
- Merchant tạo đơn hàng thông qua API Pay2S.
- Người dùng tiến hành thanh toán.
- Khi hệ thống Pay2S xác nhận giao dịch, một yêu cầu HTTP POST sẽ được gửi đến IPN URL mà merchant đã cấu hình.
- Thông báo IPN chứa dữ liệu về giao dịch:
transactionId
,orderId
,amount
,status
,signature
, ... - Merchant cần xác thực chữ ký (
signature
) để đảm bảo dữ liệu không bị giả mạo. - Merchant phản hồi HTTP status code
200 OK
để xác nhận đã nhận thành công.
Attribute | Value | Description |
---|---|---|
URL | ipnUrl | URL |
Method | POST | Phương thức gửi IPN |
Headers | Content-type: application/json | HTTP Headers |
Payload | Nội dung JSON giao dịch | Thông tin giao dịch được gửi từ Pay2S |
Mẫu Request IPN
json
curl -X 'POST' 'https://example.com/pay2s_ipn' -H 'content-type: application/json' -d $'{
"partnerCode":"PAY2S",
"orderId":"01234567890123451633504872421",
"requestId":"01234567890123451633504872421",
"amount":1000,
"orderInfo":"Test Thue 1234556",
"orderType":"Pay2S_wallet",
"transId":2588659987,
"resultCode":0,
"message":"Giao dịch thành công.",
"payType":"qr",
"signature":"90482b3881bdf863d5f61ace078921bbc6dbb58b2fded35261c71c9af3b1ce4f"
}'
Response của đối tác sẽ đến Pay2S sau khi Pay2S gửi HTTP request đến địa chỉ ipnUrl
.
IMPORTANT
Đối tác cần phản hồi lại trong vòng 30 giây với:
- HTTP status code:
200 OK
- Body:
{ "success": true }
(dưới dạng JSON)
Nếu không đúng cấu trúc trên, Pay2S sẽ tự động gửi lại IPN tối đa 5 lần.
INFO
Đối tác cần kiểm tra tính hợp lệ của chữ ký trong IPN để đảm bảo kết quả của giao dịch. Amount
và Currency
trong notification phải khớp với Amount
và Currency
đối tác lưu trong database bên đó!
Field resultCode và message do đối tác xử lý. Tham chiếu đến result code mà Pay2S trả về. Pay2S sẽ dùng những thông tin này để phản hồi lại khách hàng nếu có bất cứ lỗi nào xảy ra trong quá trình xử lý thanh toán bên nhà đối tác.
Trạng Thái Giao Dịch Sử dụng field resultCode để xác định trạng thái của giao dịch:
resultCode = 0
: giao dịch thành công.resultCode = 9000
: giao dịch được cấp quyền (authorization) thành công .resultCode <> 0
: giao dịch thất bại. Tham khảo Result code để xác định chi tiết lỗi của giao dịch.
📘 Một số resultCode
phổ biến
resultCode | Mô tả |
---|---|
0 | Giao dịch thành công |
9000 | giao dịch được cấp quyền (authorization) thành công. |
2 | Giao dịch đã hủy hoặc không thành công |
1002 | Sai chữ ký xác thực |
1003 | Giao dịch không tồn tại |
💻 Code mẫu
php
<?php
header("content-type: application/json; charset=UTF-8");
http_response_code(200); //200 - Everything will be 200 Oke
$rawData = file_get_contents('php://input');
$data = json_decode($rawData, true);
if (is_null($data)) {
echo 'Invalid JSON data.';
exit;
}
function response($data, $code = 200)
{
http_response_code($code);
header('Content-Type: application/json');
echo json_encode($data);
die();
}
$accessKey = "";
$secretKey = "";
$response = array();
try {
$amount = $data['amount'];
$extraData = isset($data['extraData']) ? $data['extraData'] : '';
$message = $data['message'];
$orderId = $data['orderId'];
$orderInfo = $data['orderInfo'];
$orderType = $data['orderType'];
$partnerCode = $data['partnerCode'];
$payType = $data['payType'];
$requestId = $data['requestId'];
$responseTime = $data['responseTime'];
$resultCode = $data['resultCode'];
$transId = $data['transId'];
$signature = $data['signature'];
//Checksum
$rawHash = "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";
$partnerSignature = hash_hmac("sha256", $rawHash, $secretKey);
if ($signature == $partnerSignature) {
if ($resultCode == '0') {
$result = '<div class="alert alert-success">Capture Payment Success</div>';
} else {
$result = '<div class="alert alert-danger">' . $message . '</div>';
}
} else {
$result = '<div class="alert alert-danger">This transaction could be hacked, please check your signature and returned signature</div>';
}
} catch (Exception $e) {
echo $response['message'] = $e;
}
$debugger = array();
$debugger['rawData'] = $rawHash;
$debugger['pay2sSignature'] = $signature;
$debugger['partnerSignature'] = $partnerSignature;
if ($signature == $partnerSignature) {
$response['success'] = true;
$response['message'] = "Received payment result success";
} else {
$response['success'] = false;
$response['message'] = "ERROR! Fail checksum";
}
$response['debugger'] = $debugger;
echo json_encode($response);
js
const express = require("express");
const crypto = require("crypto");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.post("/ipn", (req, res) => {
const data = req.body;
// Kiểm tra JSON hợp lệ
if (!data) {
return res
.status(400)
.json({ success: false, message: "Invalid JSON data." });
}
let accessKey = "";
let secretKey = "";
try {
// Lấy dữ liệu từ request body
const amount = data.amount;
const extraData = data.extraData || "";
const message = data.message;
const orderId = data.orderId;
const orderInfo = data.orderInfo;
const orderType = data.orderType;
const partnerCode = data.partnerCode;
const payType = data.payType;
const requestId = data.requestId;
const responseTime = data.responseTime;
const resultCode = data.resultCode;
const transId = data.transId;
const signature = data.signature;
// Tạo chuỗi `rawHash` cho chữ ký
const rawHash = `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}`;
// Tạo chữ ký HMAC SHA256
const partnerSignature = crypto
.createHmac("sha256", secretKey)
.update(rawHash)
.digest("hex");
// Kiểm tra chữ ký
if (signature === partnerSignature) {
if (resultCode === "0") {
result =
'<div class="alert alert-success">Capture Payment Success</div>';
} else {
result = `<div class="alert alert-danger">${message}</div>`;
}
} else {
result =
'<div class="alert alert-danger">This transaction could be hacked, please check your signature and returned signature</div>';
}
// Debugger thông tin
const debuggerInfo = {
rawData: rawHash,
pay2sSignature: signature,
partnerSignature: partnerSignature,
};
// Trả về kết quả
if (signature === partnerSignature) {
return res.status(200).json({
success: true,
message: "Received payment result success",
debugger: debuggerInfo,
});
} else {
return res.status(200).json({
success: false,
message: "ERROR! Fail checksum",
debugger: debuggerInfo,
});
}
} catch (error) {
return res.status(500).json({ success: false, message: error.message });
}
});
// Khởi động server
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
js
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Pay2SIPN.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class Pay2SController : ControllerBase
{
[HttpPost("ipn")]
public IActionResult IPN([FromBody] Pay2SRequest data)
{
// Kiểm tra JSON hợp lệ
if (data == null)
{
return BadRequest(new { success = false, message = "Invalid JSON data." });
}
string accessKey = ""; // Khóa truy cập của bạn
string secretKey = ""; // Khóa bí mật của bạn
string partnerSignature;
try
{
// Lấy các giá trị từ request body
var amount = data.amount;
var extraData = data.extraData ?? string.Empty;
var message = data.message;
var orderId = data.orderId;
var orderInfo = data.orderInfo;
var orderType = data.orderType;
var partnerCode = data.partnerCode;
var payType = data.payType;
var requestId = data.requestId;
var responseTime = data.responseTime;
var resultCode = data.resultCode;
var transId = data.transId;
var signature = data.signature;
// Tạo chuỗi rawHash
var rawHash = $"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}";
// Tạo chữ ký HMAC SHA256
partnerSignature = CreateHmacSha256Signature(rawHash, secretKey);
// Kiểm tra chữ ký
if (signature == partnerSignature)
{
if (resultCode == "0")
{
return Ok(new
{
success = true,
message = "Received payment result success",
debugger = new
{
rawData = rawHash,
pay2sSignature = signature,
partnerSignature = partnerSignature
}
});
}
else
{
return Ok(new
{
success = false,
message = message,
debugger = new
{
rawData = rawHash,
pay2sSignature = signature,
partnerSignature = partnerSignature
}
});
}
}
else
{
return Ok(new
{
success = false,
message = "This transaction could be hacked, please check your signature and returned signature",
debugger = new
{
rawData = rawHash,
pay2sSignature = signature,
partnerSignature = partnerSignature
}
});
}
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = ex.Message });
}
}
// Hàm tạo chữ ký HMAC SHA256
private string CreateHmacSha256Signature(string data, string secretKey)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
// Lớp đại diện cho dữ liệu yêu cầu từ Pay2S
public class Pay2SRequest
{
public string amount { get; set; }
public string extraData { get; set; }
public string message { get; set; }
public string orderId { get; set; }
public string orderInfo { get; set; }
public string orderType { get; set; }
public string partnerCode { get; set; }
public string payType { get; set; }
public string requestId { get; set; }
public string responseTime { get; set; }
public string resultCode { get; set; }
public string transId { get; set; }
public string signature { get; set; }
}
}
🔧 Instant Payment Notification (IPN)
Instant Payment Notification (IPN) là thông điệp được gửi từ Nhà Cung Cấp Dịch Vụ Thanh Toán - Payment Service Provider (PSP) đến Bên Sử Dụng Dịch Vụ Thanh Toán - Payment Service Consumer (PSC). Việc này sử dụng giao thức HTTP và quy trình này là bất đồng bộ.
Tại sao sử dụng IPN
Instant Payment Notification (IPN) được sử dụng để thông báo (notify) kết quả giao dịch ngay lâp tức dến PSC:
Thanh Toán: Web, SmartTv.
Thanh Toán Trực Tuyến
Thanh Toán Định Kỳ Đối tác có thể sử dụng hệ thống của mình để xử lý thông tin nhận được từ PSP:
Cập nhật trạng thái giao dịch (khuyên dùng)
Cập nhật số dư tài khoản (nạo tiền), xuất sản phẩm, thông báo kết quả giao dịch đến ứng dụng di động,...
Gửi hóa đơn điện tử Sử dụng IPN để khắc phục trường hợp thanh toán thành công nhưng người dùng không nhận được sản phẩm. Lý do:
Người dùng đóng trình duyệt
Không thể điều hướng đến trang ban đầu của đối tác: do đường truyền, hệ thông quá tải,...
Cách thực thi IPN
Đối tác cần tạo API phía backend để 'lắng nghe' kết quả từ Pay2S và cung cấp URL của API đó trong field ipnUrl.
Sau khi người dùng sử dụng thanh toán trên Pay2S (web hoặc app), Pay2S sẽ thông báo kết quả giao dịch ngay lập tức đến URL này. API của đối tác nhận kết quả thanh toán và tiếp tực xử lý trong hệ thông bên họ.
Xây dựng API
HTTP Produces Header: Sử dụng để Pay2S gửi HTTP Request Content-Type:
application/json
HTTP Consumers Header: Đối tác phản hồi lại Pay2S:
HTTP code 200
.Endpoint: URL chỉ nên bao gồm đường dẫn, tham số và không chứa ký tự đặc biệt, unicode hoặc khoảng cách.