Chuyển tới nội dung chính

THÔNG SỐ KỸ THUẬT API HOÀN TIỀN GLODIPAY

PHIÊN BẢN 2.0.0

Mục lục

Giới thiệu

Tài liệu này mô tả GLODIPAY Refund API v2, cho phép người bán khởi tạo hoàn tiền cho các giao dịch đã hoàn tất, truy vấn trạng thái hoàn tiền và nhận thông báo hoàn tiền qua IPN.

Các điểm chính:

  • Hoàn tiền có thể là toàn bộ hoặc một phần (tùy thuộc vào các hạn chế của từng nhà cung cấp -- một số nhà cung cấp chỉ hỗ trợ hoàn tiền toàn bộ số tiền).
  • Việc hoàn tiền có thể được xử lý tự động ngay lập tức hoặc tạm dừng để phê duyệt thủ công, tùy thuộc vào cấu hình của người bán.
  • notificationUrl từ giao dịch gốc được sử dụng cho các thông báo IPN hoàn tiền.
  • Số tiền hoàn lại tối đa cho mỗi lần hoàn tiền là: paidAmount trừ đi tổng số tiền đã được hoàn lại trước đó cho giao dịch.

Điểm cuối (Endpoints)

Thử nghiệmSẽ được cung cấp
Sản xuấtLấy từ trang API Keys trong Dashboard của người bán

Chữ ký (Signature)

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 để xác minh tính xác thực.

Chữ ký sử dụng RSA với MD5 (md5WithRSAEncryption).

Tạo chữ ký (Người bán -> GLODIPAY)

Sử dụng khóa riêng của người bán (có sẵn trong Dashboard của người bán).

Các bước:

  • Thu thập tất cả các tham số yêu cầu ngoại trừ signature dưới dạng đối tượng khóa-giá trị phẳng.
  • Sắp xếp chỉ các khóa cấp cao nhất theo thứ tự tăng dần tự nhiên (SORT_NATURAL trong PHP / localeCompare với { numeric: true } trong JavaScript). Không sắp xếp các khóa bên trong các đối tượng lồng nhau.
  • Cắt bỏ khoảng trắng ở đầu và cuối tất cả các giá trị chuỗi một cách đệ quy.
  • Tuần tự hóa thành một chuỗi JSON.
  • Ký bằng openssl_sign(..., 'md5WithRSAEncryption').
  • Mã hóa Base64 đầu ra nhị phân.

Xác minh chữ ký (GLODIPAY -> Người bán)

Sử dụng khóa công khai của GLODIPAY (có sẵn từ Cổng thông tin).

Các bước:

  • Loại bỏ signature khỏi payload.
  • Sắp xếp chỉ các khóa cấp cao nhất theo thứ tự tăng dần tự nhiên. Không sắp xếp các khóa bên trong các đối tượng lồng nhau.
  • Cắt bỏ khoảng trắng ở đầu và cuối tất cả các giá trị chuỗi một cách đệ quy.
  • Tuần tự hóa thành một chuỗi JSON.
  • Xác minh bằng openssl_verify(..., base64_decode($signature), $publicKey, 'md5WithRSAEncryption').
  • Giá trị trả về 1 = hợp lệ.

Lưu ý (Node.js): Chuyển đổi tất cả các giá trị số thành chuỗi trước khi sắp xếp/tuần tự hóa. Thoát các dấu gạch chéo xuôi trong chuỗi JSON: .replace(///g, '\/').

REFUND API (API Hoàn tiền)

Khởi tạo hoàn tiền cho một giao dịch đã hoàn tất.

Endpoint: POST /v2/refund Phương thức: POST Content-Type: application/json

Yêu cầu (Request)

transactionIdString (ULID)MID giao dịch nhận được từ IPN thanh toán hoặc truy vấn giao dịch
amountFloatMSố tiền hoàn lại. Tối thiểu: 0.10. Tối đa: số tiền còn lại có thể hoàn trả (paidAmount trừ đi số tiền đã hoàn trả)
reasonString(max:1000)OMô tả ngắn gọn lý do hoàn tiền
signatureString(max:750)MChữ ký RSA-MD5

Lưu ý: Một số nhà cung cấp nhất định (ví dụ: PayAgency, SmartPay, ClisaPay, FinvyPay, WPay) chỉ hỗ trợ hoàn tiền toàn bộ số tiền. Hệ thống sẽ thực thi số tiền hoàn lại tối thiểu tương ứng.

Phản hồi (Response)

Content-Type: application/json

statusStringsuccess
messageStringThông báo dễ hiểu cho người dùng
dataJSONChi tiết hoàn tiền. Xem đối tượng data bên dưới

đối tượng data:

refundIdString (ULID)ID hoàn tiền của GLODIPAY
refundNumberStringSố hoàn tiền dễ hiểu của GLODIPAY

Hoàn tiền đã được tạo và xử lý ngay lập tức (đã bật tự động hoàn tiền):

{
"status": "success",
"message": null,
"data": {
"refundId": "01jzabk09xc4pbgwe8hyg4cwbf",
"refundNumber": "2507-1751420414"
}
}

Hoàn tiền đã được tạo và đang chờ phê duyệt thủ công:

{
"status": "success",
"message": "Refund created and waiting for approval.",
"data": {
"refundId": "01jzabk09xc4pbgwe8hyg4cwbf",
"refundNumber": "2507-1751420414"
}
}

Lỗi xác thực (HTTP 422):

{
"status": "error",
"message": "Invalid request data.",
"errors": [
{
"field": "amount",
"message": ["The amount must be between 0.1 and 100."]
}
]
}

REFUND QUERY (Truy vấn hoàn tiền)

Truy vấn trạng thái mới nhất của một khoản hoàn tiền.

Endpoint: POST /v2/refund/query Phương thức: POST Content-Type: application/json

Yêu cầu (Request)

refundIdString (ULID)MID hoàn tiền của GLODIPAY (từ phản hồi Refund API hoặc Refund IPN)
signatureString(max:750)MChữ ký RSA-MD5

Phản hồi (Response)

Content-Type: application/json

statusStringsuccess
messageStringThông báo dễ hiểu cho người dùng
dataJSONĐối tượng chi tiết hoàn tiền. Xem các trường bên dưới

Các trường trong đối tượng data:

transactionIdStringID giao dịch gốc của GLODIPAY
refStringorderRef của người bán
refundIdStringID hoàn tiền của GLODIPAY
currencyStringMã tiền tệ ISO 4217
refundAmountFloatSố tiền hoàn lại
statusStringTrạng thái hoàn tiền. Xem phần Giá trị trạng thái hoàn tiền
statusCodeNumberMã trạng thái số. Xem phần Mã trạng thái
metadataJSONCác cặp khóa-giá trị từ phiên thanh toán gốc
reasonStringLý do hoàn tiền
messageStringThông báo trạng thái dễ hiểu
originalRefundCreatedAtISO 8601 datetimeThời gian tạo hoàn tiền tại PSP
refundCreatedAtISO 8601 datetimeThời gian tạo hoàn tiền trong hệ thống GLODIPAY
transactionCreatedAtISO 8601 datetimeThời gian tạo giao dịch gốc
signatureStringChữ ký RSA-MD5 -- xác minh bằng khóa công khai của GLODIPAY

Ví dụ:

{
"status": "success",
"message": "",
"data": {
"transactionId": "01jza90dy6w82dfrrqvadn5vs4",
"ref": "ORDER-001",
"refundId": "01jzabk09xc4pbgwe8hyg4cwbf",
"currency": "USD",
"refundAmount": 50.00,
"status": "refund_successful",
"statusCode": 11,
"metadata": { "orderId": "12345" },
"reason": "Customer request",
"message": null,
"originalRefundCreatedAt": "2026-04-14T11:00:00+00:00",
"refundCreatedAt": "2026-04-14T11:00:01+00:00",
"transactionCreatedAt": "2026-04-14T10:00:00+00:00",
"signature": "base64-encoded-rsa-signature"
}
}

REFUND NOTIFICATION (Thông báo hoàn tiền)

GLODIPAY gửi một HTTP POST đến notificationUrl của giao dịch gốc khi trạng thái hoàn tiền thay đổi.

Phương thức: POST Content-Type: application/json

Chính sách gửi lại (Retry policy): Nếu máy chủ của bạn không trả về {"returnCode":"100"} trong vòng 30 giây, GLODIPAY sẽ thử gửi lại.

Payload

transactionIdStringMID giao dịch gốc của GLODIPAY
refStringMorderRef của người bán
refundIdStringMID hoàn tiền của GLODIPAY
currencyStringMMã tiền tệ ISO 4217
refundAmountFloatMSố tiền hoàn lại
statusStringMTrạng thái hoàn tiền. Xem phần Giá trị trạng thái hoàn tiền
statusCodeNumberMMã trạng thái số. Xem phần Mã trạng thái
metadataJSONOCác cặp khóa-giá trị từ phiên thanh toán gốc
reasonStringOLý do hoàn tiền
messageStringOThông báo trạng thái dễ hiểu
originalRefundCreatedAtISO 8601 datetimeMThời gian tạo hoàn tiền tại PSP
refundCreatedAtISO 8601 datetimeMThời gian tạo hoàn tiền trong hệ thống GLODIPAY
transactionCreatedAtISO 8601 datetimeMThời gian tạo giao dịch gốc
signatureStringMChữ ký RSA-MD5 -- xác minh bằng khóa công khai của GLODIPAY

Ví dụ Payload IPN:

{
"transactionId": "01jza90dy6w82dfrrqvadn5vs4",
"ref": "ORDER-001",
"refundId": "01jzabk09xc4pbgwe8hyg4cwbf",
"currency": "USD",
"refundAmount": 50.00,
"status": "refund_successful",
"statusCode": 11,
"metadata": { "orderId": "12345" },
"reason": "Customer request",
"message": null,
"originalRefundCreatedAt": "2026-04-14T11:00:00+00:00",
"refundCreatedAt": "2026-04-14T11:00:01+00:00",
"transactionCreatedAt": "2026-04-14T10:00:00+00:00",
"signature": "base64-encoded-rsa-signature"
}

Phản hồi (Người bán -> GLODIPAY)

Máy chủ của bạn phải phản hồi trong vòng 30 giây:

{
"returnCode": "100",
"description": "Received"
}
returnCodeStringRPhải là "100" để xác nhận đã nhận
descriptionString(1,1500)OMô tả tùy chọn

Phụ lục

Giá trị trạng thái hoàn tiền (Refund Status Values)

Giá trị chuỗi được trả về trong trường status của payload IPN hoàn tiền và các phản hồi truy vấn hoàn tiền.

refund_initiatedYêu cầu hoàn tiền đã khởi tạo
refund_under_reviewHoàn tiền đang xem xét
refund_successfulHoàn tiền hoàn tất thành công
refund_failedHoàn tiền thất bại
refund_partially_successfulHoàn tiền một phần thành công
refund_partially_failedHoàn tiền một phần thất bại
void_initiatedViệc hủy bỏ đã khởi tạo
void_under_reviewViệc hủy bỏ đang xem xét
void_successfulViệc hủy bỏ hoàn tất thành công
void_failedViệc hủy bỏ thất bại
void_partially_successfulViệc hủy bỏ một phần thành công
void_partially_failedViệc hủy bỏ một phần thất bại

Mã trạng thái (Status Codes)

Mã số trong trường statusCode của payload IPN hoàn tiền và các phản hồi truy vấn hoàn tiền.

8refund_initiatedHoàn tiền đã khởi tạo
9refund_failedHoàn tiền thất bại
10refund_under_reviewHoàn tiền đang xem xét
11refund_successfulHoàn tiền thành công
12refund_partially_failedHoàn tiền một phần thất bại
13refund_partially_successfulHoàn tiền một phần thành công
18void_initiatedVoid đã khởi tạo
19void_under_reviewVoid đang xem xét
20void_successfulVoid thành công
21void_failedVoid thất bại
22void_partially_successfulVoid một phần thành công
23void_partially_failedVoid một phần thất bại

Ví dụ mã nguồn

PHP

<?php

function generateSignature(array $data): string
{
$privateKey = openssl_pkey_get_private("-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_HERE
-----END PRIVATE KEY-----
");

ksort($data, SORT_NATURAL);
array_walk_recursive(
$data,
static function (&$field) {
$field = trim($field);
}
);

openssl_sign(json_encode($data), $signature, $privateKey, 'md5WithRSAEncryption');

return base64_encode($signature);
}

function verifySignature(array $data): bool
{
$publicKey = openssl_pkey_get_public("-----BEGIN PUBLIC KEY-----
YOUR_GLODIPAY_PUBLIC_KEY_HERE
-----END PUBLIC KEY-----
");

$dataWithoutSignature = array_filter($data, static function ($key) {
return $key !== 'signature';
}, ARRAY_FILTER_USE_KEY);

$signature = $data['signature'];

ksort($dataWithoutSignature, SORT_NATURAL);
array_walk_recursive(
$dataWithoutSignature,
static function (&$field) {
$field = trim($field);
}
);

$result = openssl_verify(
json_encode($dataWithoutSignature),
base64_decode($signature),
$publicKey,
'md5WithRSAEncryption'
);

return $result === 1;
}

// Ví dụ: khởi tạo hoàn tiền
$payload = [
'transactionId' => '01jza90dy6w82dfrrqvadn5vs4',
'amount' => '50.00',
'reason' => 'Customer request',
];

$payload['signature'] = generateSignature($payload);

$ch = curl_init('https://payment-sandbox.gpayprocessing.com/v2/refund');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);
echo 'Refund ID: ' . $result['data']['refundId'];

Node.js

// Save as script.mjs and run: node script.mjs
import { createSign } from 'crypto';
import https from 'https';

const PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_HERE
-----END PRIVATE KEY-----`;

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) {
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(PRIVATE_KEY, 'base64');
}

// Ví dụ: khởi tạo hoàn tiền
const payload = {
transactionId: '01jza90dy6w82dfrrqvadn5vs4',
amount: '50.00',
reason: 'Customer request',
};

payload.signature = generateSignature(payload);

const postData = JSON.stringify(payload);
const options = {
hostname: 'payment-sandbox.gpayprocessing.com',
path: '/v2/refund',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
},
};

const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
const result = JSON.parse(body);
console.log('Refund ID:', result.data.refundId);
});
});

req.on('error', console.error);
req.write(postData);
req.end();

Bookmarks

No bookmarks yet.
Hover over a heading and click to save a section.