GLODIPAY REFUND API SPECIFICATION
VERSION 2.0.0
Table of Contents
- Introduction
- Endpoints
- Signature
- REFUND API
- REFUND QUERY
- REFUND NOTIFICATION
- Appendix
- Refund Status Values
- Status Codes
- Code Examples
- PHP
- Node.js
Introduction
This document describes the GLODIPAY Refund API v2, which allows merchants to initiate refunds for completed transactions, query refund status, and receive refund notifications via IPN.
Key points:
- Refunds can be full or partial (subject to per-provider constraints -- some providers only support full-amount refunds).
- Refunds may be auto-processed immediately or held for manual approval, depending on merchant configuration.
- The
notificationUrlfrom the original transaction is used for refund IPN notifications. - The maximum refundable amount per refund is:
paidAmountminus the total already refunded for the transaction.
Endpoints
| Test | Get it from the API Keys page of the Sandbox Merchant Dashboard |
| Production | Get it from the API Keys page of the Merchant Dashboard |
Signature
All requests and responses are signed using RSA with MD5 to ensure integrity and authenticity.
Generating a Signature (Merchant -> GLODIPAY)
Sign request payloads with your RSA Private Key (obtained from the Merchant Dashboard).
Steps:
- Collect all request parameters except
signatureas a flat key-value object. - Sort the keys in natural ascending order (
SORT_NATURAL/localeComparewithnumeric: true). - Trim whitespace from all string values (recursive).
- Serialize to JSON string with all non-ASCII Unicode characters escaped to
\uXXXX(RFC 8259). - Sign with
md5WithRSAEncryptionusing your RSA Private Key. - Base64-encode the binary output.
Verifying a Signature (GLODIPAY -> Merchant)
Verify GLODIPAY responses and webhooks with the RSA Public Key (available in the Portal).
Steps:
- Separate
signaturefrom the payload. - Sort remaining keys in natural ascending order.
- Trim all string values (recursive).
- Serialize to JSON string with all non-ASCII Unicode characters escaped to
\uXXXX(RFC 8259). - Verify with
md5WithRSAEncryptionusing your RSA Public Key. - Return value
1= valid.
Note (Node.js): Convert all numeric values to strings before sorting/serializing. Escape forward slashes in the JSON string: .replace(///g, '/').
Note (Unicode / RFC 8259): The JSON payload must escape all non-ASCII Unicode characters (e.g., "a with accent" must become "\u00e1") before signing -- this is required by RFC 8259. In PHP,
json_encode($data)does this by default -- do not useJSON_UNESCAPED_UNICODE. In Node.js,JSON.stringify()does not escape Unicode by default -- apply:.replace(/[^\\x00-\\x7F]/g, c => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"))after serializing.
REFUND API
Initiate a refund for a completed transaction.
Endpoint: POST /v2/refund
Method: POST
Content-Type: application/json
Request
| transactionId | String (ULID) | M | The transactionId received from the checkout IPN or transaction query |
| amount | Float | M | Refund amount. Minimum: 0.10. Maximum: remaining refundable amount (paidAmount minus already refunded) |
| reason | String(max:1000) | O | Short description of the refund reason |
| signature | String(max:750) | M | RSA-MD5 signature |
Note: Certain providers (e.g. PayAgency, SmartPay, ClisaPay, FinvyPay, WPay) only support full-amount refunds. The system enforces the minimum refund amount accordingly.
Response
Content-Type: application/json
| status | String | success |
| message | String | Human-readable message |
| data | JSON | Refund details. See data object below |
data object:
| refundId | String (ULID) | GLODIPAY refund ID |
| refundNumber | String | GLODIPAY human-readable refund number |
Refund created and processed immediately (auto-refund enabled):
{
"status": "success",
"message": null,
"data": {
"refundId": "01jzabk09xc4pbgwe8hyg4cwbf",
"refundNumber": "2507-1751420414"
}
}
Refund created and pending manual approval:
{
"status": "success",
"message": "Refund created and waiting for approval.",
"data": {
"refundId": "01jzabk09xc4pbgwe8hyg4cwbf",
"refundNumber": "2507-1751420414"
}
}
Validation error (HTTP 422):
{
"status": "error",
"message": "Invalid request data.",
"errors": [
{
"field": "amount",
"message": ["The amount must be between 0.1 and 100."]
}
]
}
REFUND QUERY
Query the latest status of a refund.
Endpoint: POST /v2/refund/query
Method: POST
Content-Type: application/json
Request
| refundId | String (ULID) | M | GLODIPAY refund ID (from Refund API response or Refund IPN) |
| signature | String(max:750) | M | RSA-MD5 signature |
Response
Content-Type: application/json
| status | String | success |
| message | String | Human-readable message |
| data | JSON | Refund details object. See fields below |
data object fields:
| transactionId | String | GLODIPAY original transaction ID |
| ref | String | Merchant's orderRef |
| refundId | String | GLODIPAY refund ID |
| currency | String | ISO 4217 currency code |
| refundAmount | Float | Refund amount |
| status | String | Refund status. See Refund Status Values |
| statusCode | Number | Numeric status code. See Status Codes |
| metadata | JSON | Key-value pairs from the original checkout session |
| reason | String | Refund reason |
| message | String | Human-readable status message |
| originalRefundCreatedAt | ISO 8601 datetime | Refund creation time at the PSP |
| refundCreatedAt | ISO 8601 datetime | Refund creation time in GLODIPAY system |
| transactionCreatedAt | ISO 8601 datetime | Original transaction creation time |
| signature | String | RSA-MD5 signature -- verify with GLODIPAY public key |
Example:
{
"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 sends an HTTP POST to the notificationUrl of the original transaction when a refund status changes.
Method: POST
Content-Type: application/json
Retry policy: If your server does not return {"returnCode":"100"} within 30 seconds, GLODIPAY will retry delivery.
Payload
| transactionId | String | M | GLODIPAY original transaction ID |
| ref | String | M | Merchant's orderRef |
| refundId | String | M | GLODIPAY refund ID |
| currency | String | M | ISO 4217 currency code |
| refundAmount | Float | M | Refund amount |
| status | String | M | Refund status. See Refund Status Values |
| statusCode | Number | M | Numeric status code. See Status Codes |
| metadata | JSON | O | Key-value pairs from the original checkout session |
| reason | String | O | Refund reason |
| message | String | O | Human-readable status message |
| originalRefundCreatedAt | ISO 8601 datetime | M | Refund creation time at the PSP |
| refundCreatedAt | ISO 8601 datetime | M | Refund creation time in GLODIPAY system |
| transactionCreatedAt | ISO 8601 datetime | M | Original transaction creation time |
| signature | String | M | RSA-MD5 signature -- verify with GLODIPAY public key |
Example 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"
}
Response (Merchant -> GLODIPAY)
Your server must respond within 30 seconds:
{
"returnCode": "100",
"description": "Received"
}
| returnCode | String | R | Must be "100" to acknowledge receipt |
| description | String(1,1500) | O | Optional description |
Appendix
Refund Status Values
String values returned in the status field of refund IPN payloads and refund query responses.
| refund_initiated | Refund request initiated |
| refund_under_review | Refund under review |
| refund_successful | Refund completed successfully |
| refund_failed | Refund failed |
| refund_partially_successful | Partial refund completed |
| refund_partially_failed | Partial refund failed |
| void_initiated | Void initiated |
| void_under_review | Void under review |
| void_successful | Void completed successfully |
| void_failed | Void failed |
| void_partially_successful | Partial void completed |
| void_partially_failed | Partial void failed |
Status Codes
Numeric code in the statusCode field of refund IPN payloads and refund query responses.
| 8 | refund_initiated | Refund initiated |
| 9 | refund_failed | Refund failed |
| 10 | refund_under_review | Refund under review |
| 11 | refund_successful | Refund successful |
| 12 | refund_partially_failed | Partial refund failed |
| 13 | refund_partially_successful | Partial refund successful |
| 18 | void_initiated | Void initiated |
| 19 | void_under_review | Void under review |
| 20 | void_successful | Void successful |
| 21 | void_failed | Void failed |
| 22 | void_partially_successful | Partial void successful |
| 23 | void_partially_failed | Partial void failed |
Code Examples
PHP
<?php
function generateSignature(array $data): string
{
$privateKey = openssl_pkey_get_private("-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_HERE
-----END PRIVATE KEY-----
");
foreach ($data as $k => $v) {
if (is_array($v)) $data[$k] = json_encode($v);
}
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;
}
// Example: create a refund
$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');
}
// Example: create a refund
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();