跳到主要内容

GLODIPAY 退款 API 规范

版本 2.0.0

目录

介绍

本文档描述了 GLODIPAY 退款 API v2,它允许商户为已完成的交易发起退款、查询退款状态并通过 IPN 接收退款通知。

关键点:

  • 退款可以是全额部分(取决于各渠道的限制 —— 某些渠道仅支持全额退款)。
  • 根据商户配置,退款可能会被立即自动处理保留以待人工审核
  • 原始交易的 notificationUrl 用于接收退款 IPN 通知。
  • 每次退款的最大可退金额为:paidAmount 减去该交易已退款的总额。

接口地址 (Endpoints)

测试环境将另行提供
生产环境从商户后台的 API Keys 页面获取

签名 (Signature)

所有发送给 GLODIPAY 的请求必须包含 signature 字段。所有来自 GLODIPAY 的响应和 IPN Payload 也包含 signature 字段以验证真实性。

签名使用 RSA with MD5 (md5WithRSAEncryption)。

生成签名 (商户 -> GLODIPAY)

使用您的 商户私钥(可从商户后台获取)。

步骤:

  • 将除 signature 以外的所有请求参数收集为扁平的键值对象。
  • 仅按 自然升序顶层键 进行排序(PHP 中的 SORT_NATURAL / JavaScript 中带有 { numeric: true }localeCompare)。不要对嵌套对象内部的键进行排序。
  • 递归修整所有字符串值的空格。
  • 序列化为 JSON 字符串。
  • 使用 openssl_sign(..., 'md5WithRSAEncryption') 进行签名。
  • 对二进制输出进行 Base64 编码。

验证签名 (GLODIPAY -> 商户)

使用 GLODIPAY 公钥(可在门户网站获取)。

步骤:

  • 从 Payload 中移除 signature
  • 仅按自然升序对 顶层键 进行排序。不要对嵌套对象内部的键进行排序。
  • 递归修整所有字符串值的空格。
  • 序列化为 JSON 字符串。
  • 使用 openssl_verify(..., base64_decode($signature), $publicKey, 'md5WithRSAEncryption') 进行验证。
  • 返回值 1 = 有效。

注意 (Node.js):在排序/序列化之前将所有数字值转换为字符串。转义 JSON 字符串中的正斜杠:.replace(///g, '\/')。

退款 API (REFUND API)

为已完成的交易发起退款。

接口地址: POST /v2/refund 请求方法: POST Content-Type: application/json

请求 (Request)

transactionIdString (ULID)M从结账 IPN 或交易查询接收到的 transactionId
amountFloatM退款金额。最小值:0.10。最大值:剩余可退款金额(paidAmount 减去已退款金额)
reasonString(max:1000)O退款原因的简短描述
signatureString(max:750)MRSA-MD5 签名

注意:某些渠道(如 PayAgency, SmartPay, ClisaPay, FinvyPay, WPay)仅支持全额退款。系统将相应地强制执行最小退款金额。

响应 (Response)

Content-Type: application/json

statusStringsuccess
messageString人类可读的消息
dataJSON退款详情。请参阅下面的 data 对象

data 对象:

refundIdString (ULID)GLODIPAY 退款 ID
refundNumberStringGLODIPAY 人类可读的退款编号

退款已创建并立即处理(启用了自动退款):

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

退款已创建并等待人工审核:

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

验证错误 (HTTP 422):

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

退款查询 (REFUND QUERY)

查询退款的最新状态。

接口地址: POST /v2/refund/query 请求方法: POST Content-Type: application/json

请求 (Request)

refundIdString (ULID)MGLODIPAY 退款 ID(来自退款 API 响应或退款 IPN)
signatureString(max:750)MRSA-MD5 签名

响应 (Response)

Content-Type: application/json

statusStringsuccess
messageString人类可读的消息
dataJSON退款详情对象。请参阅下面的字段

data 对象字段:

transactionIdStringGLODIPAY 原始交易 ID
refString商户的 orderRef
refundIdStringGLODIPAY 退款 ID
currencyStringISO 4217 货币代码
refundAmountFloat退款金额
statusString退款状态。请参阅“退款状态值”部分
statusCodeNumber数字状态码。请参阅“状态码”部分
metadataJSON原始结账会话中的键值对
reasonString退款原因
messageString人类可读的状态消息
originalRefundCreatedAtISO 8601 datetime支付渠道侧的退款创建时间
refundCreatedAtISO 8601 datetimeGLODIPAY 系统中的退款创建时间
transactionCreatedAtISO 8601 datetime原始交易创建时间
signatureStringRSA-MD5 签名 —— 使用 GLODIPAY 公钥验证

示例:

{
"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)

当退款状态发生变化时,GLODIPAY 会向原始交易的 notificationUrl 发送 HTTP POST 请求。

请求方法: POST Content-Type: application/json

重试策略: 如果您的服务器未能在 30 秒 内返回 {"returnCode":"100"},GLODIPAY 将重试发送。

Payload

transactionIdStringMGLODIPAY 原始交易 ID
refStringM商户的 orderRef
refundIdStringMGLODIPAY 退款 ID
currencyStringMISO 4217 货币代码
refundAmountFloatM退款金额
statusStringM退款状态。请参阅“退款状态值”部分
statusCodeNumberM数字状态码。请参阅“状态码”部分
metadataJSONO原始结账会话中的键值对
reasonStringO退款原因
messageStringO人类可读的状态消息
originalRefundCreatedAtISO 8601 datetimeM支付渠道侧的退款创建时间
refundCreatedAtISO 8601 datetimeMGLODIPAY 系统中的退款创建时间
transactionCreatedAtISO 8601 datetimeM原始交易创建时间
signatureStringMRSA-MD5 签名 —— 使用 GLODIPAY 公钥验证

IPN Payload 示例:

{
"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"
}

响应 (商户 -> GLODIPAY)

您的服务器必须在 30 秒 内响应:

{
"returnCode": "100",
"description": "Received"
}
returnCodeStringR必须为 "100" 以确认收到
descriptionString(1,1500)O可选描述

附录

退款状态值

在退款 IPN Payload 和退款查询响应的 status 字段中返回的字符串值。

refund_initiated退款请求已发起
refund_under_review退款审核中
refund_successful退款成功完成
refund_failed退款失败
refund_partially_successful部分退款完成
refund_partially_failed部分退款失败
void_initiated撤销已发起
void_under_review撤销审核中
void_successful撤销成功完成
void_failed撤销失败
void_partially_successful部分撤销完成
void_partially_failed部分撤销失败

状态码

在退款 IPN Payload 和退款查询响应的 statusCode 字段中的数字代码。

8refund_initiated退款已发起
9refund_failed退款失败
10refund_under_review退款审核中
11refund_successful退款成功
12refund_partially_failed部分退款失败
13refund_partially_successful部分退款成功
18void_initiated撤销已发起
19void_under_review撤销审核中
20void_successful撤销成功
21void_failed撤销失败
22void_partially_successful部分撤销成功
23void_partially_failed部分撤销失败

代码示例

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

// 示例:发起退款
$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');
}

// 示例:发起退款
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.