EPaySe
Guide

Handle Refunds

Learn how to process full and partial refunds programmatically via the API or through the merchant dashboard.

Refund Types

Full Refund

Refund the entire transaction amount back to the customer. The transaction status changes to REFUND_SUCCESS.

Partial Refund

Refund a portion of the transaction. Multiple partial refunds can be issued until the full amount is refunded.

Refund Processing Flow

1

Create Refund

Call POST /api/v1/refund/create with transaction ID and amount

2

Validation

System validates transaction status, refund amount, and merchant balance

3

Async Processing

Refund queued for processing at PSP (status: REFUND_INIT)

4

Webhook Notification

Webhook sent with the result: refund.completed or refund.failed. A failed refund can be retried by our team (you then get refund.retried and a new result for the same refund id).

Create a Refund

Request Parameters

ParameterTypeRequiredDescription
transactionIdstring
Required
ULID of the original transaction
refundAmountnumber
Required
Amount to refund (min: $0.10, max: remaining refundable)
reasonstring
Optional
Reason for refund (max 255 characters)

API Request

Bash
Create Refund (cURL)
curl -X POST https://gateway.epayse.com/api/v1/refund/create \
  -H "Content-Type: application/json" \
  -H "X-Api-Key-Id: your_api_key" \
  -H "X-Signature: generated_hmac_signature" \
  -H "X-Timestamp: 1700000000" \
  -H "X-Nonce: unique_nonce_value" \
  -H "X-Signature-Version: 3" \
  -d '{
    "transactionId": "01kbkzs3pdcdkjvsq2xb2j0ej3",
    "refundAmount": 50.00,
    "reason": "Customer request"
  }'

Response

JSON
Success Response (200)
{
  "status": "SUCCESS",
  "message": "Refund created successfully",
  "data": {
    "id": "01kbm1234abcd5678efgh9012",
    "transaction_id": "01kbkzs3pdcdkjvsq2xb2j0ej3",
    "amount": 50.00,
    "currency": "USD",
    "status": "processing",
    "reason": "Customer request",
    "created_at": "2024-01-15T10:30:00Z",
    "total_fee": 3.50,
    "completed_at": null,
    "provider_created_at": "2024-01-15T10:30:00Z"
  }
}

Check Refund Status

Poll the refund status using the GET endpoint (prefer webhooks for real-time updates):

Bash
Get Refund Details (cURL)
curl -X GET https://gateway.epayse.com/api/v1/refund/01kbm1234abcd5678efgh9012 \
  -H "X-Api-Key-Id: your_api_key" \
  -H "X-Signature: generated_hmac_signature" \
  -H "X-Timestamp: 1700000000" \
  -H "X-Nonce: unique_nonce_value" \
  -H "X-Signature-Version: 3"

PHP Integration Example

PHP
Create Refund (PHP)
<?php

$apiKey = 'your_api_key';
$secretKey = 'your_secret_key';

$timestamp = time();
$nonce = bin2hex(random_bytes(16));

// Full refund
$payload = json_encode([
    'transactionId' => '01kbkzs3pdcdkjvsq2xb2j0ej3',
    'refundAmount' => 100.00,    // Full transaction amount
    'reason' => 'Customer request',
]);

// Generate the HMAC-SHA256 signature (version 3): METHOD|PATH|TIMESTAMP|NONCE|BODY
$path = 'api/v1/refund/create';   // no leading slash
$signature = hash_hmac('sha256', implode('|', ['POST', $path, $timestamp, $nonce, $payload]), $secretKey);

$ch = curl_init('https://gateway.epayse.com/api/v1/refund/create');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-Api-Key-Id: ' . $apiKey,
        'X-Signature: ' . $signature,
        'X-Timestamp: ' . $timestamp,
        'X-Nonce: ' . $nonce,
        'X-Signature-Version: 3',
    ],
    CURLOPT_RETURNTRANSFER => true,
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($response['status'] === 'SUCCESS') {
    $refundId = $response['data']['id'];
    // Store refund ID - wait for webhook to confirm completion
    echo "Refund initiated: $refundId (status: processing)";
}

Refund Status Lifecycle

StatusDescriptionFinal?
REFUND_INIT
Refund created, queued for processing at PSPNo
REFUND_SUCCESS
Full refund completed successfullyYes
REFUND_PARTIAL_SUCCESS
Partial refund completed (more refunds possible)Yes
REFUND_FAIL
Refund rejected by PSPYes

Webhook Events

Listen for refund webhook events to track the final result:

JSON
Refund Webhook Events
// Webhook event: refund.completed
{
  "id": "evt_01hqkz7n3b5v9x2c4d6f8g0h1j",
  "object": "event",
  "type": "refund.completed",
  "created": "2024-01-15T10:31:00Z",
  "livemode": true,
  "api_key_id": "01hqkz7n3b5v9x2c4d6f8g0h1j",
  "data": {
    "object": {
      "id": "01kbm1234abcd5678efgh9012",
      "object": "refund",
      "transaction_id": "01kbkzs3pdcdkjvsq2xb2j0ej3",
      "amount": 50.00,
      "currency": "USD",
      "status": "REFUND_SUCCESS",
      "reason": "Customer request"
    }
  },
  "api_version": "2024-01-01"
}

// Webhook event: refund.failed  (same envelope, type "refund.failed", status "REFUND_FAIL")

Validation Rules

Amount

Minimum 0.01, maximum is the remaining refundable amount of the transaction. Max 2 decimal places. Only one refund per transaction can be in progress at a time. Refunds are possible for 90 days after the payment and are refused while the transaction has an active dispute.

Transaction

Must exist in your merchant account and have status SUCCESS. Cannot refund PENDING, FAIL, CANCEL, or EXPIRE transactions.

Balance

The refund and its fee are held from your available balance when it starts. If your balance does not cover them, the request is still accepted (200) with approvalStatus: PENDING_BALANCE and waits for an administrator; nothing is sent to the provider meanwhile. At most 50 refunds can wait at once. Send an X-Idempotency-Key header so a repeated request does not create a second refund.

Testing Refunds

Use these sandbox test cards to simulate refund scenarios:

Card NumberResultUse Case
4000 0000 0000 5126
Success
Full refund processed successfully
4000 0000 0000 5423
Partial
Partial refund processed successfully

Related Resources

Explore the full API documentation for refund endpoint specifications.