Refund Payin
Initiate a refund request on a completed payin (with received status).
Before initiating the refund, check if the transaction is refundable (is_refundable == true).
Note:
If you initiate a refund immediately after a successful payin, the refund must be full. Partial refunds are only supported 30 minutes to 1 hour after the payin. Please keep this in mind when implementing your refund flow.
curl --request POST \
--url https://api.cleverhub.co/api/v2/payins/refund \
--header 'Content-Type: application/json' \
--header 'app-id: <api-key>' \
--header 'secret-key: <api-key>' \
--data '
{
"uuid": "APU221YS",
"description": "Partial Refund Reason",
"amount": 10,
"external_id": "REFUND-10001"
}
'import requests
url = "https://api.cleverhub.co/api/v2/payins/refund"
payload = {
"uuid": "APU221YS",
"description": "Partial Refund Reason",
"amount": 10,
"external_id": "REFUND-10001"
}
headers = {
"app-id": "<api-key>",
"secret-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'app-id': '<api-key>',
'secret-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
uuid: 'APU221YS',
description: 'Partial Refund Reason',
amount: 10,
external_id: 'REFUND-10001'
})
};
fetch('https://api.cleverhub.co/api/v2/payins/refund', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cleverhub.co/api/v2/payins/refund",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'uuid' => 'APU221YS',
'description' => 'Partial Refund Reason',
'amount' => 10,
'external_id' => 'REFUND-10001'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"app-id: <api-key>",
"secret-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cleverhub.co/api/v2/payins/refund"
payload := strings.NewReader("{\n \"uuid\": \"APU221YS\",\n \"description\": \"Partial Refund Reason\",\n \"amount\": 10,\n \"external_id\": \"REFUND-10001\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("app-id", "<api-key>")
req.Header.Add("secret-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cleverhub.co/api/v2/payins/refund")
.header("app-id", "<api-key>")
.header("secret-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"uuid\": \"APU221YS\",\n \"description\": \"Partial Refund Reason\",\n \"amount\": 10,\n \"external_id\": \"REFUND-10001\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cleverhub.co/api/v2/payins/refund")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["app-id"] = '<api-key>'
request["secret-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"uuid\": \"APU221YS\",\n \"description\": \"Partial Refund Reason\",\n \"amount\": 10,\n \"external_id\": \"REFUND-10001\"\n}"
response = http.request(request)
puts response.read_body{
"uuid": "TYK80AI9",
"name": "Hello Clever",
"email": "test@example.com",
"external_id": null,
"status": "partially_refunded",
"pay_code": {},
"currency": "VND",
"amount": "1000000.0",
"total": "1000000.0",
"paid_amount": "1000000.0",
"is_refundable": true,
"payment_method": "vn_other_vietqr_vnd",
"expired_at": "",
"webhook_notification": {
"endpoint_url": "https://webhook.site/789d4a41-9a6a-4cd7-925e-6db7bddb13c3",
"authorization_header": "****"
},
"description": "Test Payin",
"gst": false,
"gst_amount": 0,
"pay_by": "2025-06-06T07:57:20.464+0000",
"sender_details": null,
"stage": "normal_stage",
"refund_information": {
"total_amount": "1000000.0",
"refund_amount": "10.0",
"description": "1234566"
}
}{
"errors": {
"code": "REQUIRE_LOGIN",
"message": "Not Authorised"
}
}{
"errors": {
"payment_request_id": "is invalid",
"refund_amount": "can't be blank",
"reason": "can't be blank"
}
}Authorizations
A unique identifier assigned to each application.
A secure token associated with the app-id.
Body
Payment request ID
"APU221YS"
Partial refund reason, must be at least 5 characters long
"Partial Refund Reason"
Refund amount, must be numeric. If not specified, the refund amount will be the same as the total amount.
10
Your own reference ID for this refund. Use it to match the refund to a record in your system.
"REFUND-10001"
Response
Refund Started
"TYK80AI9"
"Hello Clever"
"test@example.com"
null
"partially_refunded"
{}
"VND"
"1000000.0"
"1000000.0"
"1000000.0"
true
"vn_other_vietqr_vnd"
""
Show child attributes
Show child attributes
"Test Payin"
false
0
"2025-06-06T07:57:20.464+0000"
"normal_stage"
Show child attributes
Show child attributes
curl --request POST \
--url https://api.cleverhub.co/api/v2/payins/refund \
--header 'Content-Type: application/json' \
--header 'app-id: <api-key>' \
--header 'secret-key: <api-key>' \
--data '
{
"uuid": "APU221YS",
"description": "Partial Refund Reason",
"amount": 10,
"external_id": "REFUND-10001"
}
'import requests
url = "https://api.cleverhub.co/api/v2/payins/refund"
payload = {
"uuid": "APU221YS",
"description": "Partial Refund Reason",
"amount": 10,
"external_id": "REFUND-10001"
}
headers = {
"app-id": "<api-key>",
"secret-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'app-id': '<api-key>',
'secret-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
uuid: 'APU221YS',
description: 'Partial Refund Reason',
amount: 10,
external_id: 'REFUND-10001'
})
};
fetch('https://api.cleverhub.co/api/v2/payins/refund', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cleverhub.co/api/v2/payins/refund",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'uuid' => 'APU221YS',
'description' => 'Partial Refund Reason',
'amount' => 10,
'external_id' => 'REFUND-10001'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"app-id: <api-key>",
"secret-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cleverhub.co/api/v2/payins/refund"
payload := strings.NewReader("{\n \"uuid\": \"APU221YS\",\n \"description\": \"Partial Refund Reason\",\n \"amount\": 10,\n \"external_id\": \"REFUND-10001\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("app-id", "<api-key>")
req.Header.Add("secret-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cleverhub.co/api/v2/payins/refund")
.header("app-id", "<api-key>")
.header("secret-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"uuid\": \"APU221YS\",\n \"description\": \"Partial Refund Reason\",\n \"amount\": 10,\n \"external_id\": \"REFUND-10001\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cleverhub.co/api/v2/payins/refund")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["app-id"] = '<api-key>'
request["secret-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"uuid\": \"APU221YS\",\n \"description\": \"Partial Refund Reason\",\n \"amount\": 10,\n \"external_id\": \"REFUND-10001\"\n}"
response = http.request(request)
puts response.read_body{
"uuid": "TYK80AI9",
"name": "Hello Clever",
"email": "test@example.com",
"external_id": null,
"status": "partially_refunded",
"pay_code": {},
"currency": "VND",
"amount": "1000000.0",
"total": "1000000.0",
"paid_amount": "1000000.0",
"is_refundable": true,
"payment_method": "vn_other_vietqr_vnd",
"expired_at": "",
"webhook_notification": {
"endpoint_url": "https://webhook.site/789d4a41-9a6a-4cd7-925e-6db7bddb13c3",
"authorization_header": "****"
},
"description": "Test Payin",
"gst": false,
"gst_amount": 0,
"pay_by": "2025-06-06T07:57:20.464+0000",
"sender_details": null,
"stage": "normal_stage",
"refund_information": {
"total_amount": "1000000.0",
"refund_amount": "10.0",
"description": "1234566"
}
}{
"errors": {
"code": "REQUIRE_LOGIN",
"message": "Not Authorised"
}
}{
"errors": {
"payment_request_id": "is invalid",
"refund_amount": "can't be blank",
"reason": "can't be blank"
}
}