API reference
Refunds
Refund a payment and look up a refund. Card refunds are full refunds, and are reviewed before they complete.
| Endpoint | Purpose |
|---|---|
| POST/api/v1/refunds/create | Refund a payment |
| POST/api/v1/refunds/query | Look up a refund |
Create a refund
POST/api/v1/refunds/create| Field | Type | Required | Description |
|---|---|---|---|
original_order_no | string | One of these two | HansaPay's order number of the payment |
original_merchant_order_no | string | One of these two | Your order number of the payment |
merchant_refund_no | string | Yes | Your refund number, at most 64 characters. See Idempotency. |
refund_amount | string | Yes | The amount to refund, in the payment's original currency, for example "12.50" |
reason | string | No | At most 255 characters |
notify_url | string | No | Where to send refund.updated webhooks. If you leave it out, you receive no refund webhooks. |
metadata | string | No | Returned in the refund webhook |
{
"original_merchant_order_no": "ORDER-1001-069I",
"merchant_refund_no": "REFUND-1001-069I",
"refund_amount": "25.00",
"reason": "Customer cancelled the subscription",
"notify_url": "https://merchant.example.com/hansapay/webhook",
"metadata": "ticket-5521"
}BODY='{"original_merchant_order_no":"ORDER-1001-069I","merchant_refund_no":"REFUND-1001-069I","refund_amount":"25.00","reason":"Customer cancelled the subscription","notify_url":"https://merchant.example.com/hansapay/webhook","metadata":"ticket-5521"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/refunds/create \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/refunds/create", {
original_merchant_order_no: "ORDER-1001-069I",
merchant_refund_no: "REFUND-1001-069I",
refund_amount: "25.00",
reason: "Customer cancelled the subscription",
notify_url: "https://merchant.example.com/hansapay/webhook",
metadata: "ticket-5521"
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/refunds/create", {
"original_merchant_order_no": "ORDER-1001-069I",
"merchant_refund_no": "REFUND-1001-069I",
"refund_amount": "25.00",
"reason": "Customer cancelled the subscription",
"notify_url": "https://merchant.example.com/hansapay/webhook",
"metadata": "ticket-5521",
})
res = json.loads(raw)POST /api/v1/refunds/create HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420276
X-Nonce: 78990fe7fada4754
X-Sign: 1299634987c26fbc230219198c881ee157f0be82814f5af030afd41ebf173d3d
{
"original_merchant_order_no": "ORDER-1001-069I",
"merchant_refund_no": "REFUND-1001-069I",
"refund_amount": "25.00",
"reason": "Customer cancelled the subscription",
"notify_url": "https://merchant.example.com/hansapay/webhook",
"metadata": "ticket-5521"
}{
"code": 0,
"msg": "success",
"data": {
"refund_no": "362191056193720320",
"merchant_refund_no": "REFUND-1001-069I",
"original_order_no": "362191050694987776",
"original_merchant_order_no": "ORDER-1001-069I",
"status": "PENDING",
"refund_amount": {
"currency": "USD",
"value": "25.00"
},
"created_at": "2026-09-26T18:57:56+08:00"
}
}Response fields: refund_no, merchant_refund_no, original_order_no, original_merchant_order_no, status, refund_amount and created_at.
Rules:
- The payment must be paid (
SUCCESS, or already partly refunded) and must not have a chargeback. - The total of your refunds on a payment cannot exceed the payment amount.
- Your available balance must cover the refund plus the refund fee. The amount is held from your balance while the refund is pending, and released if it fails.
- On card, Apple Pay and Google Pay payments, only one full refund is possible (see What to expect).
A refund that is refused when you create it returns an error. The refund number is used up even so: HansaPay records the refused refund with status FAILED, and you can look it up by merchant_refund_no.
Query a refund
POST/api/v1/refunds/queryPOST/api/v1/refunds/query with exactly one of refund_no or merchant_refund_no.
{
"merchant_refund_no": "REFUND-1001-069I"
}BODY='{"merchant_refund_no":"REFUND-1001-069I"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/refunds/query \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/refunds/query", {
merchant_refund_no: "REFUND-1001-069I"
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/refunds/query", {
"merchant_refund_no": "REFUND-1001-069I",
})
res = json.loads(raw)POST /api/v1/refunds/query HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420277
X-Nonce: 43dbcb1276ee2ad5
X-Sign: 68032f8d9bf975d67c3a23b037b35c204711b05eda666a923719c4f27c61a134
{
"merchant_refund_no": "REFUND-1001-069I"
}{
"code": 0,
"msg": "success",
"data": {
"refund_no": "362191056193720320",
"merchant_refund_no": "REFUND-1001-069I",
"original_order_no": "362191050694987776",
"original_merchant_order_no": "ORDER-1001-069I",
"status": "SUCCESS",
"refund_amount": {
"currency": "USD",
"value": "25.00"
},
"reason": "Customer cancelled the subscription",
"created_at": "2026-09-26T18:57:56+08:00",
"updated_at": "2026-09-26T18:57:56+08:00"
}
}Response fields: refund_no, merchant_refund_no, original_order_no, original_merchant_order_no, status, refund_amount, reason, created_at and updated_at.
Refund statuses
| Status | Meaning |
|---|---|
PENDING | Submitted and waiting for review or for the provider |
SUCCESS | The money has been returned to the payer |
FAILED | The refund did not happen; any held amount has been released |
HansaPay checks pending refunds with the provider every few minutes, so you do not need to poll. A refund can become FAILED when:
- the review rejects it;
- the provider reports it as failed or closed;
- the provider has no record of it 30 minutes after it was submitted.
