Skip to main content

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

FieldTypeRequiredDescription
txNostring✅ YesTransaction number of the original captured payment
amountdecimal✅ YesAmount to refund (must be greater than 0)
reasonstring❌ NoReason for the refund (stored in UserDefinedField1)
descriptionstring❌ NoAdditional 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

FieldTypeDescription
refundTxNostringUnique transaction number for this refund
originalTxNostringTransaction number of the original payment
amountdecimalAmount refunded
currencystringCurrency code (inherited from original transaction)
statusstringRefund status (Pending, Captured, Failed, Declined)
messagestringStatus message from the payment processor
providerRefundIdstringRefund ID from the payment provider (if available)
createdAtdatetimeTimestamp 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

  1. Always verify the transaction status before initiating a refund

    const transaction = await getTransactionStatus(txNo);
    if (transaction.status === 'Captured') {
    await processRefund(txNo, amount);
    }
  2. Keep track of partial refunds in your system

    // Store refund records
    await db.refunds.create({
    originalTxNo: txNo,
    refundTxNo: result.refundTxNo,
    amount: amount,
    reason: reason
    });
  3. 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');
    }
    });
  4. Provide reason for audit trail

    await processRefund(txNo, amount, 'Customer requested - Order #12345');

❌ DON'T

  1. Don't refund without checking transaction status

    // ❌ BAD
    await processRefund(txNo, amount);

    // ✅ GOOD
    if (await canRefund(txNo)) {
    await processRefund(txNo, amount);
    }
  2. 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));
  3. 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)
Check Refund Status

Use the Check Transaction Status endpoint with the refundTxNo to verify refund processing status.

Important

Refunds can only be processed for transactions in Captured status. Pending or Authorized transactions cannot be refunded - they should be cancelled/voided instead.

Provider Support

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

ScenarioAmountExpected Result
Full refundOriginal amountStatus: Captured, Original status → Refunded
Partial refund50% of originalStatus: Captured, Original status → Captured
Exceed amount> OriginalError: Amount exceeds original
Second full refundOriginal amountError: Exceeds remaining refundable
Refund pending TXAny amountError: Must be Captured status

Support

For questions about refunds or specific provider capabilities: