Chữ ký
Tất cả các yêu cầu gửi đến GLODIPAY phải bao gồm trường signature. Tất cả các phản hồi và payload IPN từ GLODIPAY cũng bao gồm trường signature để bạn có thể xác minh tính xác thực.
Chữ ký sử dụng thuật toán RSA với MD5 (md5WithRSAEncryption).
RSA Private Key và RSA Public Key đều có sẵn trong trang API Keys trong Dashboard của người bán.
Tạo chữ ký (Merchant → GLODIPAY)
Ký các payload yêu cầu bằng RSA Private Key của bạn (lấy từ Merchant Dashboard).
Các bước:
- Lọc Payload: Thu thập tất cả các tham số yêu cầu ngoại trừ
signature. Loại bỏ bất kỳ khóa nào có giá trị là chuỗi rỗng (""),nullhoặcundefined. - Chuyển đổi dữ liệu lồng nhau thành chuỗi: Nếu giá trị của một trường là một đối tượng hoặc mảng (ví dụ:
metadata,browserDetails,transactionDocuments), hãy chuyển đổi nó thành chuỗi JSON bằng bộ mã hóa mặc định của ngôn ngữ bạn đang dùng (không có khoảng trắng). - Sắp xếp các khóa cấp cao nhất: Sắp xếp các khóa kết quả theo thứ tự tăng dần tự nhiên (phân biệt chữ hoa chữ thường). Sử dụng
SORT_NATURALtrong PHP hoặclocaleCompare(b, undefined, { numeric: true, sensitivity: 'base' })trong JavaScript. - Ép kiểu và Cắt bỏ khoảng trắng (Casting & Trimming):
- Chuyển đổi số thành chuỗi.
- Chuyển đổi kiểu boolean:
truethành"1",falsethành chuỗi rỗng"". - Cắt bỏ khoảng trắng ở đầu và cuối (trim) tất cả các giá trị.
- Chuẩn hóa (Canonicalization): Tuần tự hóa đối tượng đã sắp xếp thành một chuỗi JSON với tất cả các ký tự Unicode không phải ASCII được thoát thành
\uXXXX(RFC 8259). Trong PHP,json_encode($data)thực hiện điều này theo mặc định — không sử dụngJSON_UNESCAPED_UNICODE. Trong Node.js,JSON.stringify()không thoát Unicode theo mặc định — áp dụng sửa lỗi bên dưới.- Node.js — dấu gạch chéo xuôi: Áp dụng
.replace(/\//g, '\\/')để khớp với hành vi mặc định của PHP. - Node.js — Unicode (RFC 8259): Áp dụng
.replace(/[\u0080-\uffff]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))để thoát tất cả các ký tự không phải ASCII. Điều này bắt buộc vìJSON.stringify()giữ nguyên các ký tự nhưá,đ,ễdưới dạng UTF-8 thô, trong khi PHP thoát chúng thànhá,đ,ễ. Nếu không có sửa lỗi này, chữ ký sẽ không khớp khi payload chứa các ký tự không phải ASCII.
- Node.js — dấu gạch chéo xuôi: Áp dụng
- Ký (Signing): Ký chuỗi chuẩn hóa bằng thuật toán
md5WithRSAEncryption. - Mã hóa (Encoding): Mã hóa Base64 đầu ra nhị phân và thêm nó vào trường
signature.
Xác minh chữ ký (GLODIPAY → Merchant)
Sử dụng RSA Public Key (có trong Portal) để xác minh các phản hồi và webhook của GLODIPAY.
Các bước:
- Trích xuất giá trị
signaturevà loại bỏ khóasignaturekhỏi payload. - Thực hiện các bước Lọc, Chuyển đổi chuỗi, Sắp xếp và Cắt bỏ khoảng trắng tương tự như mô tả ở trên.
- Xác minh chuỗi chuẩn hóa với chữ ký đã trích xuất bằng RSA Public Key và thuật toán
md5WithRSAEncryption.
Ví dụ mã nguồn
PHP
<?php
function generateSignature(array $data, string $privateKeyStr): string
{
unset($data['signature']);
// 1. Filter out empty/null values
$data = array_filter($data, function($v) {
return $v !== "" && $v !== null;
});
// 2. Stringify nested objects
foreach ($data as $k => $v) {
if (is_array($v)) $data[$k] = json_encode($v);
}
// 3. Sort keys
ksort($data, SORT_NATURAL);
// 4. Casting and trimming
array_walk_recursive($data, function (&$v) {
if (is_bool($v)) $v = $v ? "1" : "";
else $v = trim((string)$v);
});
// 5. Sign (PHP json_encode escapes '/' by default)
$key = openssl_pkey_get_private($privateKeyStr);
openssl_sign(json_encode($data), $signature, $key, 'md5WithRSAEncryption');
return base64_encode($signature);
}
Node.js
// Save as script.mjs and run: node script.mjs
import { createSign } from 'crypto';
function phpCast(v) {
if (typeof v === 'number') return String(v);
if (typeof v === 'boolean') return v ? '1' : '';
if (typeof v === 'string') return v.trim();
if (Array.isArray(v)) return v.map(phpCast);
if (v && typeof v === 'object') return Object.fromEntries(Object.entries(v).map(([k, val]) => [k, phpCast(val)]));
return v;
}
function generateSignature(data, privateKey) {
const sorted = {};
Object.keys(data)
.filter(k => k !== 'signature')
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }))
.forEach(k => { sorted[k] = data[k]; });
const canonical = JSON.stringify(phpCast(sorted))
.replace(/\//g, '\\/')
.replace(/[\u0080-\uffff]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'));
const sign = createSign('md5WithRSAEncryption');
sign.update(canonical);
return sign.sign(privateKey, 'base64');
}