Refund Transaction
Process a full or partial refund for a previously captured transaction.
Endpoint
POST https://api.paysecurez.com/api/v1/transactions/refund
Authentication
Requires Bearer token authentication:
Authorization: Bearer YOUR_ACCESS_TOKEN
Request Body
{
"txNo": "txn_abc12345",
"amount": 50.00,
"reason": "Customer requested refund",
"description": "Defective product returned"
}
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
txNo | string | ✅ Yes | Transaction number of the original captured payment |
amount | decimal | ✅ Yes | Amount to refund (must be greater than 0) |
reason | string | ❌ No | Reason for the refund (stored in UserDefinedField1) |
description | string | ❌ No | Additional details about the refund (stored in UserDefinedField2) |
Response
Success Response (200 OK)
{
"refundTxNo": "txn_xyz78910",
"originalTxNo": "txn_abc12345",
"amount": 50.00,
"currency": "USD",
"status": "Pending",
"message": "Refund initiated successfully",
"providerRefundId": "ref_provider_123",
"createdAt": "2025-12-11T10:30:00Z"
}
Response Fields
| Field | Type | Description |
|---|---|---|
refundTxNo | string | Unique transaction number for this refund |
originalTxNo | string | Transaction number of the original payment |
amount | decimal | Amount refunded |
currency | string | Currency code (inherited from original transaction) |
status | string | Refund status (Pending, Captured, Failed, Declined) |
message | string | Status message from the payment processor |
providerRefundId | string | Refund ID from the payment provider (if available) |
createdAt | datetime | Timestamp when refund was created |
Refund Types
Full Refund
Refund the entire amount of the original transaction:
{
"txNo": "txn_abc12345",
"amount": 100.00,
"reason": "Order cancelled"
}
After a full refund, the original transaction status changes to Refunded.
Partial Refund
Refund only part of the original amount:
{
"txNo": "txn_abc12345",
"amount": 30.00,
"reason": "Partial item return"
}
After a partial refund, the original transaction status remains Captured, and you can issue additional refunds until the total refunded amount equals the original amount.
Validation Rules
1. Transaction Status
The original transaction MUST have status Captured to be eligible for refund.
{
"error": "Transaction must be in Captured status to be refunded. Current status: Pending"
}
2. Amount Validation
Refund amount must:
- Be greater than 0
- Not exceed the original transaction amount
- Not exceed the remaining refundable amount (if partial refunds already exist)
{
"error": "Refund amount (150.00) cannot exceed original transaction amount (100.00)"
}
3. Multiple Refunds
You can process multiple partial refunds as long as the total doesn't exceed the original amount:
Original Transaction: $100.00
Refund 1: $30.00 ✅ (Remaining: $70.00)
Refund 2: $50.00 ✅ (Remaining: $20.00)
Refund 3: $25.00 ❌ (Exceeds remaining amount)
Error Responses
Transaction Not Found (400 Bad Request)
{
"error": "Original transaction with TxNo txn_abc12345 not found"
}
Invalid Status (400 Bad Request)
{
"error": "Transaction must be in Captured status to be refunded. Current status: Pending"
}
Amount Exceeds Limit (400 Bad Request)
{
"error": "Refund amount (50.00) exceeds remaining refundable amount (30.00)"
}
Validation Error (400 Bad Request)
{
"errors": {
"Amount": ["Amount must be greater than 0"]
}
}
Refund Status Flow
┌──────────────────┐
│ Refund Initiated │
│ Status: Pending │
└────────┬─────────┘
│
▼
┌──────────────────────┐
│ Provider Processing │
└────────┬──────┬──────┘
│ │
┌────┘ └────┐
▼ ▼
┌─────────┐ ┌──────────┐
│ Success │ │ Failed │
│ Captured│ │ Declined │
└─────────┘ └──────────┘
Webhook Notification
After the refund is processed, a webhook notification will be sent to your configured callbackUrl (from the original transaction):
{
"txNo": "txn_xyz78910",
"status": "Captured",
"amount": 50.00,
"currency": "USD",
"transactionType": "Refund",
"originalTxNo": "txn_abc12345",
"providerTransactionId": "ref_provider_123",
"providerStatus": "success",
"providerMessage": "Refund processed successfully",
"createdAt": "2025-12-11T10:30:00Z"
}
Code Examples
cURL
curl -X POST https://api.paysecurez.com/api/v1/transactions/refund \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"txNo": "txn_abc12345",
"amount": 50.00,
"reason": "Customer requested refund",
"description": "Item returned due to defect"
}'
JavaScript/Node.js
const axios = require('axios');
async function processRefund(txNo, amount, reason) {
try {
const response = await axios.post(
'https://api.paysecurez.com/api/v1/transactions/refund',
{
txNo: txNo,
amount: amount,
reason: reason,
description: 'Refund processed via API'
},
{
headers: {
'Authorization': `Bearer ${process.env.PAYSECUREZ_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
console.log('Refund successful:', response.data);
return response.data;
} catch (error) {
console.error('Refund failed:', error.response?.data || error.message);
throw error;
}
}
// Example usage
processRefund('txn_abc12345', 50.00, 'Customer request')
.then(result => console.log('Refund TxNo:', result.refundTxNo))
.catch(err => console.error('Error:', err));
Python
import requests
import os
def process_refund(tx_no, amount, reason=None):
url = "https://api.paysecurez.com/api/v1/transactions/refund"
headers = {
"Authorization": f"Bearer {os.environ.get('PAYSECUREZ_TOKEN')}",
"Content-Type": "application/json"
}
payload = {
"txNo": tx_no,
"amount": amount,
"reason": reason,
"description": "Refund processed via Python API"
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Refund failed: {response.json()}")
# Example usage
try:
result = process_refund("txn_abc12345", 50.00, "Customer request")
print(f"Refund successful! Refund TxNo: {result['refundTxNo']}")
except Exception as e:
print(f"Error: {e}")
PHP
<?php
function processRefund($txNo, $amount, $reason = null) {
$url = "https://api.paysecurez.com/api/v1/transactions/refund";
$data = [
'txNo' => $txNo,
'amount' => $amount,
'reason' => $reason,
'description' => 'Refund processed via PHP API'
];
$options = [
'http' => [
'header' => [
"Authorization: Bearer " . getenv('PAYSECUREZ_TOKEN'),
"Content-Type: application/json"
],
'method' => 'POST',
'content' => json_encode($data)
]
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
return json_decode($response, true);
}
// Example usage
try {
$result = processRefund("txn_abc12345", 50.00, "Customer request");
echo "Refund successful! Refund TxNo: " . $result['refundTxNo'];
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
?>
Best Practices
✅ DO
-
Always verify the transaction status before initiating a refund
const transaction = await getTransactionStatus(txNo);
if (transaction.status === 'Captured') {
await processRefund(txNo, amount);
} -
Keep track of partial refunds in your system
// Store refund records
await db.refunds.create({
originalTxNo: txNo,
refundTxNo: result.refundTxNo,
amount: amount,
reason: reason
}); -
Handle webhook notifications to update refund status
app.post('/webhooks/payment', (req, res) => {
const { txNo, status, transactionType } = req.body;
if (transactionType === 'Refund' && status === 'Captured') {
// Update order status to "Refunded"
updateOrderStatus(txNo, 'refunded');
}
}); -
Provide reason for audit trail
await processRefund(txNo, amount, 'Customer requested - Order #12345');
❌ DON'T
-
Don't refund without checking transaction status
// ❌ BAD
await processRefund(txNo, amount);
// ✅ GOOD
if (await canRefund(txNo)) {
await processRefund(txNo, amount);
} -
Don't exceed the original amount
// ❌ BAD
await processRefund(txNo, 150.00); // Original was $100
// ✅ GOOD
const transaction = await getTransaction(txNo);
const maxRefund = transaction.amount - transaction.refundedAmount;
await processRefund(txNo, Math.min(requestedAmount, maxRefund)); -
Don't ignore error responses
// ❌ BAD
processRefund(txNo, amount);
// ✅ GOOD
try {
await processRefund(txNo, amount);
} catch (error) {
logError(error);
notifyAdmin(error);
}
Refund Timing
- Instant Processing: The refund request is processed immediately by the payment provider
- Settlement Time: Actual funds return to customer depends on:
- Payment provider (2-5 business days for cards)
- Customer's bank processing time
- Transaction type (card, bank transfer, wallet)
Use the Check Transaction Status endpoint with the refundTxNo to verify refund processing status.
Refunds can only be processed for transactions in Captured status. Pending or Authorized transactions cannot be refunded - they should be cancelled/voided instead.
Not all payment providers support partial refunds. Check with your account manager if you need partial refund functionality for specific providers.
Testing
Sandbox Environment
Test refunds in sandbox mode using test transaction numbers:
curl -X POST https://api.paysecurez.com/api/v1/transactions/refund \
-H "Authorization: Bearer SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"txNo": "test_txn_12345",
"amount": 10.00,
"reason": "Test refund"
}'
Test Scenarios
| Scenario | Amount | Expected Result |
|---|---|---|
| Full refund | Original amount | Status: Captured, Original status → Refunded |
| Partial refund | 50% of original | Status: Captured, Original status → Captured |
| Exceed amount | > Original | Error: Amount exceeds original |
| Second full refund | Original amount | Error: Exceeds remaining refundable |
| Refund pending TX | Any amount | Error: Must be Captured status |
Related Endpoints
- Create Transaction - Create a new payment
- Check Transaction Status - Verify transaction status
- Webhooks - Receive real-time refund notifications
Support
For questions about refunds or specific provider capabilities:
- 📧 Email: support@paysecurez.com
- 💬 Discord: https://discord.gg/paysecurez
- 📚 Docs: https://docs.paysecurez.com