Developer

Xử lý kết quả thanh toán (Payment Notification)

✧ Mở bằng AI

Sao chép tài liệu, mở AI rồi dán vào cuộc trò chuyện.

ChatGPTClaudePerplexityGrok
≡ Xem Markdown

Đối chiếu luồng hiện tại ngày 11/10/2026: callback trình duyệt là GET redirectUrl với query đã ký. responseTime tính bằng giây, không phải mili giây như IPN. requestId là ID nội bộ. extraData và transId không nằm trong chuỗi ký redirect hiện tại. Chỉ dùng trang này hiển thị kết quả; cập nhật thanh toán bền vững qua IPN hoặc kiểm tra server.

🔁 Giao diện Redirect (Client)

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 dưới dạng query parameters:

📋 Query Parameters

Parameter Loại Mô tả
partnerCode String Mã đối tác
orderId String Mã đơn hàng
requestId String Mã yêu cầu
amount String Số tiền
orderInfo String Thông tin đơn hàng
orderType String Loại đơn hàng
transId String ID giao dịch (nếu có)
resultCode String Mã kết quả (0 = thành công)
message String Thông báo kết quả
payType String Phương thức thanh toán
responseTime String Unix timestamp giây trong query
m2signature String Chữ ký xác thực

⚠️ Lưu ý quan trọng

💻 Code mẫu

<?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"]; // Pay2S signature

    // Tạo chuỗi hash để xác thực chữ ký
    $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);

    // 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>';
    }
}

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; // Pay2S 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}`);
});

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();
            }
        }
    }
}
from flask import Flask, request, render_template_string
import hmac
import hashlib

app = Flask(__name__)

def verify_signature(params, signature, secret_key):
    """Xác thực chữ ký HMAC SHA256"""
    raw_hash = f"accessKey={params.get('accessKey')}&amount={params.get('amount')}&message={params.get('message')}&orderId={params.get('orderId')}&orderInfo={params.get('orderInfo')}&orderType={params.get('orderType')}&partnerCode={params.get('partnerCode')}&payType={params.get('payType')}&requestId={params.get('requestId')}&responseTime={params.get('responseTime')}&resultCode={params.get('resultCode')}"
    
    computed_signature = hmac.new(
        secret_key.encode('utf-8'),
        raw_hash.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return computed_signature == signature

@app.route('/redirect')
def redirect_from_pay2s():
    access_key = ""  # Khóa truy cập
    secret_key = ""  # Khóa bí mật
    
    # Lấy các giá trị từ query parameters
    params = request.args.to_dict()
    
    partner_code = params.get('partnerCode')
    order_id = params.get('orderId')
    request_id = params.get('requestId')
    amount = params.get('amount')
    order_info = params.get('orderInfo')
    order_type = params.get('orderType')
    trans_id = params.get('transId', '')
    result_code = params.get('resultCode')
    message = params.get('message')
    pay_type = params.get('payType')
    response_time = params.get('responseTime')
    extra_data = params.get('extraData', '')
    m2signature = params.get('m2signature')  # Pay2S signature
    
    # Xác thực chữ ký
    if verify_signature(params, m2signature, secret_key):
        if result_code == '0':
            result = '<div class="alert alert-success"><strong>Payment status: </strong>Success</div>'
        else:
            result = f'<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>'
    
    return render_template_string('''
        <!DOCTYPE html>
        <html>
        <head>
            <title>Payment Result</title>
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
        </head>
        <body>
            <div class="container mt-5">
                {{ result | safe }}
            </div>
        </body>
        </html>
    ''', result=result)

if __name__ == '__main__':
    app.run(port=3000, debug=True)
import com.google.gson.Gson;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

@Controller
public class Pay2SRedirectController {
    
    private static String createSignature(String rawData, String secretKey) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(
            secretKey.getBytes(StandardCharsets.UTF_8),
            0,
            secretKey.getBytes(StandardCharsets.UTF_8).length,
            "HmacSHA256"
        );
        mac.init(secretKeySpec);
        byte[] hash = mac.doFinal(rawData.getBytes(StandardCharsets.UTF_8));
        
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }
    
    @GetMapping("/redirect")
    public String redirectFromPay2S(
            @RequestParam String partnerCode,
            @RequestParam String orderId,
            @RequestParam String requestId,
            @RequestParam String amount,
            @RequestParam String orderInfo,
            @RequestParam String orderType,
            @RequestParam(required = false) String transId,
            @RequestParam String resultCode,
            @RequestParam String message,
            @RequestParam String payType,
            @RequestParam String responseTime,
            @RequestParam(required = false) String extraData,
            @RequestParam String m2signature,
            Model model) {
        
        String accessKey = "";  // Khóa truy cập
        String secretKey = "";  // Khóa bí mật
        
        try {
            // Tạo chuỗi rawHash
            String rawHash = String.format(
                "accessKey=%s&amount=%s&message=%s&orderId=%s&orderInfo=%s&orderType=%s&partnerCode=%s&payType=%s&requestId=%s&responseTime=%s&resultCode=%s",
                accessKey, amount, message, orderId, orderInfo, orderType, partnerCode, payType, requestId, responseTime, resultCode
            );
            
            // Tạo chữ ký
            String partnerSignature = createSignature(rawHash, secretKey);
            
            String result;
            
            // Kiểm tra chữ ký
            if (m2signature.equals(partnerSignature)) {
                if ("0".equals(resultCode)) {
                    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>";
            }
            
            model.addAttribute("result", result);
        } catch (Exception e) {
            model.addAttribute("result", "<div class='alert alert-danger'>Error: " + e.getMessage() + "</div>");
        }
        
        return "payment-result";
    }
}
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"fmt"
	"html/template"
	"log"
	"net/http"
)

func createSignature(rawData, secretKey string) string {
	h := hmac.New(sha256.New, []byte(secretKey))
	h.Write([]byte(rawData))
	return fmt.Sprintf("%x", h.Sum(nil))
}

func redirectHandler(w http.ResponseWriter, r *http.Request) {
	accessKey := ""  // Khóa truy cập
	secretKey := ""  // Khóa bí mật
	
	// Parse query parameters
	err := r.ParseForm()
	if err != nil {
		http.Error(w, "Error parsing form", http.StatusBadRequest)
		return
	}
	
	partnerCode := r.FormValue("partnerCode")
	orderId := r.FormValue("orderId")
	requestId := r.FormValue("requestId")
	amount := r.FormValue("amount")
	orderInfo := r.FormValue("orderInfo")
	orderType := r.FormValue("orderType")
	resultCode := r.FormValue("resultCode")
	message := r.FormValue("message")
	payType := r.FormValue("payType")
	responseTime := r.FormValue("responseTime")
	m2signature := r.FormValue("m2signature")
	
	// Tạo chuỗi rawHash
	rawHash := fmt.Sprintf(
		"accessKey=%s&amount=%s&message=%s&orderId=%s&orderInfo=%s&orderType=%s&partnerCode=%s&payType=%s&requestId=%s&responseTime=%s&resultCode=%s",
		accessKey, amount, message, orderId, orderInfo, orderType, partnerCode, payType, requestId, responseTime, resultCode,
	)
	
	// Tạo chữ ký
	partnerSignature := createSignature(rawHash, secretKey)
	
	var result template.HTML
	
	// Kiểm tra chữ ký
	if m2signature == partnerSignature {
		if resultCode == "0" {
			result = template.HTML("<div class='alert alert-success'><strong>Payment status: </strong>Success</div>")
		} else {
			result = template.HTML(fmt.Sprintf("<div class='alert alert-danger'><strong>Payment status: </strong>%s</div>", message))
		}
	} else {
		result = template.HTML("<div class='alert alert-danger'>This transaction could be hacked, please check your signature and returned signature</div>")
	}
	
	// Trả về HTML
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	tmpl := template.Must(template.New("result").Parse(`
		<!DOCTYPE html>
		<html>
		<head>
			<title>Payment Result</title>
			<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
		</head>
		<body>
			<div class="container mt-5">
				{{ . }}
			</div>
		</body>
		</html>
	`))
	tmpl.Execute(w, result)
}

func main() {
	http.HandleFunc("/redirect", redirectHandler)
	log.Println("Server running on port 3000")
	log.Fatal(http.ListenAndServe(":3000", nil))
}
require 'sinatra'
require 'openssl'
require 'erb'

def verify_signature(params, signature, secret_key)
  raw_hash = "accessKey=#{params['accessKey']}&amount=#{params['amount']}&message=#{params['message']}&orderId=#{params['orderId']}&orderInfo=#{params['orderInfo']}&orderType=#{params['orderType']}&partnerCode=#{params['partnerCode']}&payType=#{params['payType']}&requestId=#{params['requestId']}&responseTime=#{params['responseTime']}&resultCode=#{params['resultCode']}"
  
  computed_signature = OpenSSL::HMAC.hexdigest('SHA256', secret_key, raw_hash)
  computed_signature == signature
end

get '/redirect' do
  access_key = ''  # Khóa truy cập
  secret_key = ''  # Khóa bí mật
  
  partner_code = params['partnerCode']
  order_id = params['orderId']
  request_id = params['requestId']
  amount = params['amount']
  order_info = params['orderInfo']
  order_type = params['orderType']
  trans_id = params['transId'] || ''
  result_code = params['resultCode']
  message = params['message']
  pay_type = params['payType']
  response_time = params['responseTime']
  extra_data = params['extraData'] || ''
  m2signature = params['m2signature']
  
  # Xác thực chữ ký
  if verify_signature(params, m2signature, secret_key)
    if result_code == '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>"
    end
  else
    result = '<div class="alert alert-danger">This transaction could be hacked, please check your signature and returned signature</div>'
  end
  
  erb :payment_result, locals: { result: result }
end

📋 Quy trình xử lý Redirect

1. Người dùng thanh toán thành công trên Pay2S
   ↓
2. Pay2S chuyển hướng người dùng đến redirectUrl với query parameters
   ↓
3. Backend nhận các tham số từ URL
   ↓
4. Xác thực chữ ký m2signature
   ↓
5. Kiểm tra resultCode:
   - 0: Giao dịch thành công → Cập nhật database
   - ≠ 0: Giao dịch thất bại → Hiển thị thông báo lỗi
   ↓
6. Hiển thị kết quả cho người dùng

🔍 Kết hợp với IPN

Khuyến cáo: Sử dụng cả hai để đảm bảo không bị mất thông tin giao dịch.

❓ Câu hỏi thường gặp

Q: Tại sao cần xác thực chữ ký?
A: Để đảm bảo yêu cầu đích thực đến từ Pay2S, không phải từ kẻ xấu giả mạo.

Q: Điều gì xảy ra nếu người dùng không nhận được redirect?
A: Pay2S sẽ gửi IPN để backend xử lý, sau đó người dùng có thể kiểm tra trạng thái thủ công.

Q: Có thể nhận được redirect nhiều lần không?
A: Có, do mạng hoặc lỗi khác. Vì thế cần kiểm tra orderId trước khi xử lý.

Q: resultCode là gì?
A: Mã trạng thái giao dịch:

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.

Payment Notification · URL quay lại

Đã gọi API demo/UAT. Đây là API dùng bởi trang payment để lấy redirectUrl. Callback tới website khách hàng là GET redirectUrl với query đã ký; không phải POST API tạo đơn.

POST /api/generate-redirect

Body:

{
  "t": "<T>"
}

HTTP 200. Response:

{
  "success": true,
  "paid": true,
  "redirectUrl": "https://developer.example.com/demos/?mode=live&accessKey=[REDACTED]&amount=250000&message=Payment+successful&orderId=DEVd33eb13817916599723201625c7&orderInfo=TT817916599723201625c7&orderType=pay2s&partnerCode=PAY2S7EPF0SB1ZP27W71&payType=pay2s&requestId=1583231&responseTime=1791659973&resultCode=0&m2signature=<REDACTED>"
}
Tìm trong 57 trang tài liệu · Esc để đóng