Appearance
Xử lý kết quả thanh toán (Payment Notification)
Giao diện (Redirect)
Sau khi luồng thanh toán hoàn tất, khách hàng được điều hướng đến redirectUrl
mà bên đối tác đã cung cấp trong create
request. Một vài thông số sẽ được thêm vào URL theo dạng sau:
Method: POST
json
redirectUrl ?{parameters}
Code mẫu
php
<?php
header('Content-type: text/html; charset=utf-8');
$accessKey = '';
$secretKey = '';
if (!empty($_GET)) {
$partnerCode = $_GET["partnerCode"];
$orderId = $_GET["orderId"];
$requestId = $_GET["requestId"];
$amount = $_GET["amount"];
$orderInfo = $_GET["orderInfo"];
$orderType = $_GET["orderType"];
$transId = $_GET["transId"] ?? '';
$resultCode = $_GET["resultCode"];
$message = $_GET["message"];
$payType = $_GET["payType"];
$responseTime = $_GET["responseTime"];
$extraData = $_GET["extraData"] ?? '';
$m2signature = $_GET["m2signature"]; //MoMo signature
//Checksum
$rawHash = "accessKey=$accessKey&amount=$amount&message=$message&orderId=$orderId&orderInfo=$orderInfo&orderType=$orderType&partnerCode=$partnerCode&payType=$payType&requestId=$requestId&responseTime=$responseTime&resultCode=$resultCode";
$partnerSignature = hash_hmac("sha256", $rawHash, $secretKey);
echo "<script>console.log('Debug huhu Objects: " . $rawHash . "' );</script>";
echo "<script>console.log('Debug huhu Objects: " . $partnerSignature . "' );</script>";
if ($m2signature == $partnerSignature) {
if ($resultCode == '0') {
$result = '<div class="alert alert-success"><strong>Payment status: </strong>Success</div>';
} else {
$result = '<div class="alert alert-danger"><strong>Payment status: </strong>' . $message . '</div>';
}
} else {
$result = '<div class="alert alert-danger">This transaction could be hacked, please check your signature and returned signature</div>';
}
}
js
const express = require('express');
const crypto = require('crypto');
const app = express();
const port = 3000;
app.get('/redirect', (req, res) => {
const accessKey = ''; // Thay bằng accessKey thực tế
const secretKey = ''; // Thay bằng secretKey thực tế
// Lấy các giá trị từ query parameters
const partnerCode = req.query.partnerCode;
const orderId = req.query.orderId;
const requestId = req.query.requestId;
const amount = req.query.amount;
const orderInfo = req.query.orderInfo;
const orderType = req.query.orderType;
const transId = req.query.transId || '';
const resultCode = req.query.resultCode;
const message = req.query.message;
const payType = req.query.payType;
const responseTime = req.query.responseTime;
const extraData = req.query.extraData || '';
const m2signature = req.query.m2signature; // MoMo signature
// Tạo chuỗi rawHash
const rawHash = `accessKey=${accessKey}&amount=${amount}&message=${message}&orderId=${orderId}&orderInfo=${orderInfo}&orderType=${orderType}&partnerCode=${partnerCode}&payType=${payType}&requestId=${requestId}&responseTime=${responseTime}&resultCode=${resultCode}`;
// Tạo chữ ký HMAC SHA256
const partnerSignature = crypto.createHmac('sha256', secretKey).update(rawHash).digest('hex');
console.log('Debug rawHash:', rawHash);
console.log('Debug partnerSignature:', partnerSignature);
let result;
// Kiểm tra chữ ký
if (m2signature === partnerSignature) {
if (resultCode === '0') {
result = '<div class="alert alert-success"><strong>Payment status: </strong>Success</div>';
} else {
result = `<div class="alert alert-danger"><strong>Payment status: </strong>${message}</div>`;
}
} else {
result = '<div class="alert alert-danger">This transaction could be hacked, please check your signature and returned signature</div>';
}
// Trả về kết quả
res.send(result);
});
// Khởi động server
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
js
using System;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
namespace Pay2SRedirect.Controllers
{
public class PaymentController : Controller
{
// Hành động xử lý redirect
[HttpGet("redirect")]
public IActionResult RedirectFromPay2S(
string partnerCode, string orderId, string requestId,
string amount, string orderInfo, string orderType,
string transId, string resultCode, string message,
string payType, string responseTime, string extraData,
string m2signature)
{
string accessKey = ""; // Thay thế với accessKey thực tế
string secretKey = ""; // Thay thế với secretKey thực tế
// Tạo chuỗi rawHash cho chữ ký
var rawHash = $"accessKey={accessKey}&amount={amount}&message={message}&orderId={orderId}&orderInfo={orderInfo}&orderType={orderType}&partnerCode={partnerCode}&payType={payType}&requestId={requestId}&responseTime={responseTime}&resultCode={resultCode}";
// Tạo chữ ký HMAC SHA256
var partnerSignature = CreateHmacSha256Signature(rawHash, secretKey);
// Kiểm tra chữ ký
if (m2signature == partnerSignature)
{
if (resultCode == "0")
{
// Trả về kết quả thành công
ViewBag.Result = "<div class='alert alert-success'><strong>Payment status: </strong>Success</div>";
}
else
{
// Trả về kết quả lỗi
ViewBag.Result = $"<div class='alert alert-danger'><strong>Payment status: </strong>{message}</div>";
}
}
else
{
// Thông báo chữ ký không hợp lệ
ViewBag.Result = "<div class='alert alert-danger'>This transaction could be hacked, please check your signature and returned signature</div>";
}
// Trả về view chứa kết quả
return View();
}
// 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();
}
}
}
}
IPN - Instant Payment Notification
Hệ thống của Pay2S sử dụng API được khai báo trong ipnUrl
để gửi HTTP request với cấu hình bên dưới đến hệ thống đối tác.
Method: POST
Attribute | Value | Description |
---|---|---|
URL | ipnUrl | URL |
Method | POST | Phương thức của HTTP request |
Headers | Content-type: application/json | HTTP Headers |
Payload | Result Transaction Nội dung của HTTP body |
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
.
INFO
Bên đối tác cần phản hồi với HTTP code 204 (không cần gửi thêm nội dung)!
Lưu ý: Đối tác cần phản hồi lại trong vòng 30 giây.
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.
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'];
$m2signature = $data['m2signature'];
//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 ($m2signature == $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'] = $m2signature;
$debugger['partnerSignature'] = $partnerSignature;
if ($m2signature == $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 m2signature = data.m2signature;
// 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 (m2signature === 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: m2signature,
partnerSignature: partnerSignature,
};
// Trả về kết quả
if (m2signature === 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 m2signature = data.m2signature;
// 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 (m2signature == partnerSignature)
{
if (resultCode == "0")
{
return Ok(new
{
success = true,
message = "Received payment result success",
debugger = new
{
rawData = rawHash,
pay2sSignature = m2signature,
partnerSignature = partnerSignature
}
});
}
else
{
return Ok(new
{
success = false,
message = message,
debugger = new
{
rawData = rawHash,
pay2sSignature = m2signature,
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 = m2signature,
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 m2signature { get; set; }
}
}
INSTANT PAYMENT NOTIFICATION
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 - success: true
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.