PayGate

Refunds

Create and manage refunds for payments.

Refunds API

The Refunds API allows you to refund charges that have previously been made.

The Refund Object

{
  "id": "re_abc123def456",
  "object": "refund",
  "amount": 5000,
  "currency": "GHS",
  "status": "succeeded",
  "payment": "pay_xyz789",
  "reason": "requested_by_customer",
  "failure_reason": null,
  "metadata": {},
  "created_at": "2024-01-15T10:00:00Z"
}

Attributes

AttributeTypeDescription
idstringUnique identifier
objectstringAlways "refund"
amountintegerRefund amount in pesewas
currencystringCurrency code
statusstringpending, succeeded, failed
paymentstringOriginal payment ID
reasonstringReason for refund
failure_reasonstringWhy the refund failed
metadataobjectCustom metadata
created_atstringCreation timestamp

Create a Refund

Refunds a payment that has been made.

POST /v1/refunds

Request Body

ParameterTypeRequiredDescription
paymentstringYesPayment ID to refund
amountintegerNoAmount to refund (defaults to full amount)
reasonstringNoReason for refund
metadataobjectNoCustom metadata

If you don't specify an amount, the full payment amount will be refunded.

# Full refund
curl -X POST https://api.44.200.142.19.nip.io/v1/refunds \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "payment": "pay_xyz789",
    "reason": "requested_by_customer"
  }'

# Partial refund
curl -X POST https://api.44.200.142.19.nip.io/v1/refunds \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "payment": "pay_xyz789",
    "amount": 2500,
    "reason": "requested_by_customer"
  }'
// Full refund
const refund = await paygate.refunds.create({
  payment: 'pay_xyz789',
  reason: 'requested_by_customer'
})

// Partial refund
const partialRefund = await paygate.refunds.create({
  payment: 'pay_xyz789',
  amount: 2500, // GHS 25.00
  reason: 'requested_by_customer'
})
# Full refund
refund = client.refunds.create(
    payment='pay_xyz789',
    reason='requested_by_customer'
)

# Partial refund
partial_refund = client.refunds.create(
    payment='pay_xyz789',
    amount=2500,
    reason='requested_by_customer'
)
// Full refund
$refund = $paygate->refunds->create([
    'payment' => 'pay_xyz789',
    'reason' => 'requested_by_customer'
]);

// Partial refund
$partialRefund = $paygate->refunds->create([
    'payment' => 'pay_xyz789',
    'amount' => 2500,
    'reason' => 'requested_by_customer'
]);

Refund Reasons

ReasonDescription
requested_by_customerCustomer requested refund
duplicateDuplicate payment
fraudulentFraudulent transaction

Retrieve a Refund

Retrieves a refund by ID.

GET /v1/refunds/:id
curl https://api.44.200.142.19.nip.io/v1/refunds/re_abc123def456 \
  -H "Authorization: Bearer sk_test_..."
const refund = await paygate.refunds.retrieve('re_abc123def456')
refund = client.refunds.retrieve('re_abc123def456')

List Refunds

Returns a list of refunds.

GET /v1/refunds

Query Parameters

ParameterTypeDescription
limitintegerNumber of results (1-100)
starting_afterstringCursor for pagination
paymentstringFilter by payment ID
created[gte]stringFilter by creation date
created[lte]stringFilter by creation date
curl "https://api.44.200.142.19.nip.io/v1/refunds?payment=pay_xyz789" \
  -H "Authorization: Bearer sk_test_..."
const refunds = await paygate.refunds.list({
  payment: 'pay_xyz789'
})
refunds = client.refunds.list(payment='pay_xyz789')

Refund Rules

Not all payments can be refunded. Consider these rules:

  1. Only succeeded payments - You can only refund payments with status: succeeded
  2. Refund window - Refunds must be made within 90 days of the original payment
  3. Partial refunds - You can refund part of a payment
  4. Multiple refunds - You can make multiple partial refunds until the full amount is refunded
  5. No reversal - Once processed, refunds cannot be canceled

Refund Statuses

StatusDescription
pendingRefund is being processed
succeededRefund was successful
failedRefund failed

Webhook Events

EventDescription
refund.createdRefund was created
refund.succeededRefund was successful
refund.failedRefund failed
app.post('/webhooks', (req, res) => {
  const event = Webhook.constructEvent(req.body, signature, secret)

  switch (event.type) {
    case 'refund.succeeded':
      const refund = event.data.object
      console.log(`Refund ${refund.id} succeeded`)
      // Update your records
      break

    case 'refund.failed':
      const failedRefund = event.data.object
      console.log(`Refund failed: ${failedRefund.failure_reason}`)
      // Handle failure
      break
  }

  res.json({ received: true })
})

Check Refundable Amount

Before creating a refund, check how much is refundable:

// Get the original payment
const payment = await paygate.payments.retrieve('pay_xyz789')

// Check refunded amount
const refunded = payment.amount_refunded || 0
const refundable = payment.amount - refunded

console.log(`Original: ${payment.amount}`)
console.log(`Refunded: ${refunded}`)
console.log(`Refundable: ${refundable}`)

if (refundable > 0) {
  const refund = await paygate.refunds.create({
    payment: payment.id,
    amount: refundable // Full remaining amount
  })
}