Refunds
Full, partial, and percentage refunds. RefundReason picker.
Refunds are initiated from the order detail page. The refund is submitted through the original payment provider and a permanent record is created.
Prerequisites
- The
ORDERS_REFUNDfeature permission assigned to your role. - The order must be in
PAID,SHIPPED,DELIVERED, orPARTIALLY_REFUNDEDstatus.PENDINGandREFUNDEDorders cannot be refunded. - An active payment provider configuration that matches the provider used to capture the original payment.
Refund types
You can refund an order in three ways:
| Type | How it works |
|---|---|
| Full refund | Submits a refund for the entire remaining refundable balance. |
| Fixed amount | Submits a refund for a specific currency amount you enter. Must be greater than zero and no more than the remaining refundable balance. |
| Percentage | Calculates a fixed amount as a percentage of the order total and submits that amount. Useful for partial compensation scenarios. |
The UI converts percentage inputs to a concrete amount before submitting.
Transaction records
Every completed refund is recorded with its status (COMPLETED or FAILED) and linked back to the original charge. The Transactions tab on the order detail page shows both the charge and all refund records together.
RefundReason picker
When submitting a refund, you must select a reason from the RefundReason tree. Reasons are managed in Settings → Refund Reasons.
Key behaviors:
- Reasons form an unlimited-depth tree.
- You must select a leaf node — a reason that has no children. Selecting a parent category is not permitted.
- Only active reasons appear in the picker at refund time. Deactivated reasons are hidden but their data is preserved on past records.
- A reason that has already been used on a refund cannot be deleted — it can only be deactivated.
- The reason text field on the refund form is optional and provides supplemental free-text context.
Refund routing
Refunds are routed to the original payment provider automatically:
- Stripe — calls the Stripe Refunds API.
- PayPal — calls the PayPal capture refund API.
After a successful refund, the order status is updated:
- Cumulative refunds less than the order total →
PARTIALLY_REFUNDED - Cumulative refunds equal to or exceeding the order total →
REFUNDED
A refund confirmation email is also queued for the customer.
Example: partial refund via the API
POST /api/order/{orderId}/refund
Authorization: <session-auth>
{
"amount": 15.00,
"reasonId": "<leaf-reason-id>",
"reason": "Optional supplemental note"
}
The response includes the created refund record and, if applicable, a note about any external sync status.
Caveats
- Partial refunds may not refund platform fees. Both Stripe and PayPal retain a portion of their processing fee when you refund a payment. The refunded amount the customer receives equals the amount you specify, but the net impact to your payout may differ from a proportional share of the original fees.
- PayPal has a refund window (typically 180 days from the original payment). Refunds attempted outside this window will fail with a provider error. If a refund is rejected for this reason, you must issue the refund outside the platform using PayPal’s merchant tools directly.
- Stripe refunds also have a time limit (within the same calendar year for most accounts). Check your Stripe dashboard settings for your specific limit.
- Refund amounts are validated server-side. Submitting an amount greater than the remaining refundable balance returns a 400 error with the maximum refundable amount included in the error message.
- Once an order is in
REFUNDEDstate, no further refunds can be submitted through the admin UI or API.
Related
- Payments overview — Stripe and PayPal configuration.
- Refund reasons — managing the reason tree in Settings.
- Order states — how refunds affect order status.