Skip to main content
POST
/
v2
/
refunds

Create Refund

Issue a full or partial refund for a completed order. Refunds are processed back to the original payment method (HSA/FSA card or regular card).

Authentication

Authorization: Bearer glm_test_YOUR_API_KEY

Request Body

{
  "order_id": "ord_abc123xyz",
  "amount_cents": 4235,
  "reason": "customer_request",
  "notes": "Customer requested refund due to delayed shipping",
  "metadata": {
    "support_ticket": "TICKET-12345"
  }
}

Parameters

ParameterTypeRequiredDescription
order_idstringYesID of the order to refund
amount_centsintegerNoAmount to refund in cents (omit for full refund)
reasonenumYesRefund reason (see below)
notesstringNoAdditional notes (max 500 chars)
metadataobjectNoCustom key-value pairs
Finding Order ID: You can get the order_id from:
  • Webhook event data (order.created event)
  • Order API response (GET /v2/orders)
  • Your database (store it when receiving order.created webhook)
  • Dashboard order details

Refund Reasons

ReasonDescriptionExample Use Case
customer_requestCustomer requested refundCustomer changed mind, no longer needed
duplicateDuplicate paymentCustomer accidentally paid twice
fraudulentFraudulent orderDetected fraudulent transaction
product_unavailableProduct out of stockItem went out of stock after order
damaged_productProduct arrived damagedItem damaged during shipping
wrong_productWrong product shippedFulfillment error, sent wrong item
otherOther reasonAny other reason (explain in notes)

Request

POST /v2/refunds

Response

{
  "id": "ref_xyz789",
  "order_id": "ord_abc123xyz",
  "amount_cents": 4235,
  "reason": "customer_request",
  "notes": "Customer requested refund due to delayed shipping",
  "status": "pending",
  "order": {
    "id": "ord_abc123xyz",
    "order_number": "ORD-2025-001234",
    "payment_status": "refunded"
  },
  "refund_breakdown": {
    "hsa_fsa_amount_cents": 2995,
    "regular_amount_cents": 1240
  },
  "metadata": {
    "support_ticket": "TICKET-12345"
  },
  "created_at": "2025-10-18T16:00:00Z",
  "processed_at": null,
  "estimated_arrival": "2025-10-25T16:00:00Z"
}

Response Fields

FieldTypeDescription
idstringUnique refund identifier
statusenumRefund status: pending, succeeded, failed, cancelled
refund_breakdownobjectSplit of refund by payment type
processed_attimestampWhen refund was processed (null if pending)
estimated_arrivaltimestampEstimated date funds return to customer

Examples

Full Refund

curl -X POST https://api.withgale.com/v2/refunds \
  -H "Authorization: Bearer glm_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ord_abc123xyz",
    "reason": "customer_request",
    "notes": "Customer not satisfied with product"
  }'

Partial Refund

curl -X POST https://api.withgale.com/v2/refunds \
  -H "Authorization: Bearer glm_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ord_abc123xyz",
    "amount_cents": 1000,
    "reason": "damaged_product",
    "notes": "Partial refund for damaged item"
  }'

With JavaScript

const createRefund = async (orderId, amount, reason, notes) => {
  const response = await fetch('https://api.withgale.com/v2/refunds', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GALE_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      order_id: orderId,
      amount_cents: amount,
      reason: reason,
      notes: notes
    })
  });

  if (!response.ok) {
    throw new Error('Failed to create refund');
  }

  const refund = await response.json();
  console.log(`Refund ${refund.id} created`);
  return refund;
};

// Full refund
await createRefund(
  'ord_abc123xyz',
  null, // null = full refund
  'customer_request',
  'Customer changed mind'
);

// Partial refund
await createRefund(
  'ord_abc123xyz',
  1500, // $15.00
  'damaged_product',
  'One item damaged in shipping'
);

Refund Status

StatusDescription
pendingRefund initiated, processing
succeededRefund processed successfully
failedRefund failed
cancelledRefund cancelled

Common Use Cases

Customer Service Refund

const refund = await createRefund({
  order_id: orderId,
  amount_cents: amount,
  reason: 'customer_request',
  notes: `Support ticket: ${ticketId}`,
  metadata: {
    support_ticket: ticketId,
    agent: req.user.id
  }
});

Cancelled Orders

// Only refund if payment was captured
if (order.payment_status === 'captured') {
  await createRefund({
    order_id: orderId,
    reason: 'other',
    notes: `Order cancelled: ${reason}`
  });
}

Partial Refund for Specific Items

// Calculate refund amount for damaged items
const refundAmount = order.line_items
  .filter(item => damagedItemIds.includes(item.id))
  .reduce((sum, item) => sum + item.price_cents * item.quantity, 0);

await createRefund({
  order_id: orderId,
  amount_cents: refundAmount,
  reason: 'damaged_product',
  notes: `Refunding items: ${damagedItemIds.join(', ')}`
});

Split Card Refunds (Automatic)

Gale automatically handles split card refunds. You don’t need to specify how much goes to each card. When an order is paid with both HSA/FSA and regular card:
{
  "order": {
    "amounts": {
      "hsa_fsa_amount_cents": 4995,  // Paid with HSA/FSA card
      "regular_amount_cents": 895,    // Paid with regular card
      "total_cents": 5890
    }
  }
}

Full Refund

Just provide the order_id - Gale splits automatically:
POST /v2/refunds
{
  "order_id": "ord_abc123",
  "reason": "customer_request"
}
Response shows the breakdown:
{
  "id": "ref_xyz789",
  "amount_cents": 5890,
  "refund_breakdown": {
    "hsa_fsa_amount_cents": 4995,  // Refunded to HSA/FSA card
    "regular_amount_cents": 895     // Refunded to regular card
  }
}

Partial Refund

Gale splits proportionally based on the original payment ratio:
POST /v2/refunds
{
  "order_id": "ord_abc123",
  "amount_cents": 2945,  // Partial refund
  "reason": "damaged_product"
}
Response:
{
  "id": "ref_xyz789",
  "amount_cents": 2945,
  "refund_breakdown": {
    "hsa_fsa_amount_cents": 2498,  // ~84.7% (same ratio as original)
    "regular_amount_cents": 447     // ~15.3%
  }
}
Key Points:
  • You only specify total amount_cents
  • Gale calculates the split automatically
  • Each amount refunds to its original payment method
  • Response shows the breakdown for your records

Webhooks

Refunds trigger the following webhook events:
{
  "type": "refund.created",
  "data": {
    "id": "ref_xyz789",
    "order_id": "ord_abc123xyz",
    "amount_cents": 4235,
    "status": "pending"
  }
}
{
  "type": "refund.succeeded",
  "data": {
    "id": "ref_xyz789",
    "order_id": "ord_abc123xyz",
    "status": "succeeded",
    "processed_at": "2025-10-18T16:05:00Z"
  }
}
See Webhooks Reference for all refund events.

Errors

Status CodeError CodeDescription
400invalid_amountRefund amount exceeds order total
400already_refundedOrder already fully refunded
400invalid_stateOrder not in refundable state
401unauthorizedInvalid or missing API key
404not_foundOrder not found
422validation_errorField validation failed

Amount Too Large

{
  "error": {
    "code": "invalid_amount",
    "message": "Refund amount exceeds order total",
    "details": {
      "requested": 10000,
      "maximum": 4235
    }
  }
}

Already Refunded

{
  "error": {
    "code": "already_refunded",
    "message": "Order has already been fully refunded"
  }
}

Invalid State

{
  "error": {
    "code": "invalid_state",
    "message": "Cannot refund order with payment status 'pending'"
  }
}

Refund Timeline

Payment MethodTypical Arrival Time
HSA/FSA Card5-10 business days
Regular Card5-10 business days
Note: Actual timing depends on the customer’s card issuer.

Best Practices

Verify Order Status

Check payment_status === 'captured' before refunding

Provide Clear Reasons

Always include reason and notes for audit trail

Notify Customers

Send email notification when refund is processed

Track in Your System

Store refund IDs and status in your database

Important Notes

Refunds cannot be undone. Once a refund is processed, it cannot be reversed. Double-check order ID and amount before submitting.
Partial refunds are supported. You can issue multiple partial refunds up to the order total. The remaining refundable amount is tracked automatically.