> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gigstack.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Record a refund or request one through Stripe

> Use the correct refund amount units and verify records separately from money movement.

**Case:** you need to refund MXN 116 from a succeeded MXN 1,160 payment. The API can
record a refund in gigstack, or also request an external refund for an eligible Stripe
payment. Choose the effect before submitting.

| `external_processor_refund` | Effect |
| - | - |
| `false` or omitted | Record the refund in gigstack; does not send money through a processor |
| `true` | Request a Stripe refund for a payment with an associated PaymentIntent, then update the gigstack record |

<Note>
  The record-only flow below was exercised on staging on **2026-10-08** with a test key:
  a fresh MXN 1,160 payment received an MXN 116 refund record, and a readback confirmed
  11,600 cumulative minor units. The Stripe path remains source-reviewed and was not
  executed with a processor. The handler multiplies the submitted amount by 100; the
  worked example uses MXN. Confirm processor
  currency support before adapting it to another currency. A credit note or cancellation
  is a separate fiscal operation, not proof that money was returned.
</Note>

## Before testing: check refund automations

Recording a refund can trigger published refund Journeys and the team’s automatic
refund handling, including actions on associated fiscal documents. A payment’s
`automation_type: "none"` does not disable these separate settings. Test payments
match published test Journeys; check those with the account owner before running a test.

Stripe test events can also reach configured webhook listeners and send team refund
notifications. `ignore_emails` on the original payment does not suppress every refund
notification. Use an approved test account with known webhook destinations and
recipients, and a fresh synthetic payment without invoice or receipt associations.

## 1. Read the original payment and choose the amount

Use Bash with `curl` and `jq`; use the [quickstart commands](/quickstart#1-set-your-environment)
to set the base URL and key, and check [their environment](/authentication#environments).
`PAYMENT_ID` must refer to the intended succeeded payment in that team
and mode. A payment merely requested or canceled is not eligible.

```bash theme={null}
set -euo pipefail
: "${GIGSTACK_BASE_URL:?Set the correct API base URL}"
: "${GIGSTACK_API_KEY:?Set the matching key locally}"
: "${PAYMENT_ID:?Set the intended succeeded payment ID}"

curl --fail-with-body "$GIGSTACK_BASE_URL/payments/$PAYMENT_ID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > before-refund.json

jq -e '.data.status == "succeeded"' before-refund.json
jq '{id: .data.id, currency: .data.currency, livemode: .data.livemode,
     total: .data.total, total_refunded_minor_units: (.data.total_refunded // 0),
     refunds: .data.refunds}' before-refund.json
```

Review existing refunds and confirm that the selected refund is not already recorded.
For this endpoint's two-decimal convention, the remaining amount in currency units is
`data.total - (data.total_refunded // 0) / 100`. The maximum considers all previous refunds.

### Keep these units separate

| Field | Units in the current implementation | MXN 116 example |
| - | - | - |
| Request `amount` | Currency units | `116` |
| Refund response `data.refund.total` | Currency units | `116` |
| Refund response `data.payment.amount` | Minor units, original payment amount | `116000` for MXN 1,160 |
| Refund response `data.payment.total_refunded` | Minor units, cumulative | `11600` after only this refund |
| GET payment `data.total` and each `refunds[].total` | Currency units | `1160` and `116` |
| GET payment `data.total_refunded` | Minor units, cumulative | `11600` |

Do not send `11600` to mean a refund of MXN 116. That would request MXN 11,600.
Use decimal-safe arithmetic in your application and serialize refund attempts for the
same payment; a client-side remaining-balance check is not a concurrency lock.

## 2. Record the authorized refund

This example records money you returned through another process. It does not initiate
a bank transfer or a Stripe refund. Confirm the payment, amount and reason before running it.

```bash theme={null}
curl --fail-with-body -X POST \
  "$GIGSTACK_BASE_URL/payments/$PAYMENT_ID/refund" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{
    "amount":116,
    "reason":"Agreed partial refund for order 1042",
    "external_processor_refund":false
  }' > refund-result.json

jq -e '.success == true and (.data.refund.total == 116)' refund-result.json
REFUND_ID=$(jq -er '.data.refund.id' refund-result.json)
export REFUND_ID
jq '{refund: .data.refund, payment: .data.payment}' refund-result.json
```

The observed record-only success was HTTP `200`. Save `data.refund.id`. The handler sets its
refund record `status` to `succeeded`; this is not independent confirmation of a bank or
processor settlement. The response's `data.payment.refund_status` is `fully_refunded`
when the cumulative amount equals the payment amount; otherwise it is `requires_action`.
That label alone does not mean this partial refund failed.

## 3. Read back the refund record

```bash theme={null}
curl --fail-with-body "$GIGSTACK_BASE_URL/payments/$PAYMENT_ID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > after-refund.json

jq -e --arg refund "$REFUND_ID" \
  '.data.refunds[] | select(.id == $refund and .total == 116)' after-refund.json
jq '{payment: .data.id, total: .data.total,
     total_refunded_minor_units: .data.total_refunded, refunds: .data.refunds}' \
  after-refund.json
```

Compare the refund ID, amount and reason, and reconcile the cumulative refunded amount
with the pre-request snapshot. The refund POST returns a gigstack refund ID, not the
Stripe refund ID. Later webhook processing may add Stripe-ID entries to the payment’s
refund list; the list does not guarantee a one-to-one mapping to money movements.

## If you intend to return money through Stripe

Only after confirming the payment has the correct Stripe PaymentIntent and account,
use the same endpoint with `external_processor_refund: true`. This is a separate choice;
do not first run the record-only example and then repeat it to send money.

The public payment response does not prove PaymentIntent eligibility. Check the original
processor record or ask the account owner. A payment without a PaymentIntent is rejected
with `400` and `External processor refund is not allowed for this payment`.

After an authorized processor refund, verify both the gigstack refund and the corresponding
refund in the connected Stripe account. The refund POST does not expose the Stripe refund ID
or use its final settlement status as a completion check. Do not report money received
by the customer solely because gigstack's record says `succeeded`.

A later Stripe webhook can change the payment status to `partial_refunded` or
`refunded` and append a processor refund entry alongside the API-created entry. Do not
count list entries as separate money movements or sum them to infer the refunded
balance. Reconcile the cumulative amount against the corresponding Stripe refund and
preserve both identifiers when asking support to investigate a mismatch.

## Duplicate attempts, failures, and timeouts

This endpoint accepts `reason`, `amount` and `external_processor_refund`; it does **not**
document an idempotency key. Never apply the payment-registration replay recipe here.

A processor request can succeed before the local update fails. After a timeout or `500`,
compare `GET /payments/{id}` and the processor history with your pre-request snapshot.
Preserve the payment ID, amount, reason, time and any refund ID. Ask the account owner or
[support](mailto:support@gigstack.io) to reconcile an ambiguous result before another POST.

For `400`, inspect the body: the payment must be succeeded, the amount must be at least
`0.01`, and cumulative refunds cannot exceed the original payment. Correct the identified
problem; do not invent a new reference to bypass a duplicate or over-refund concern.
See the [refund API contract](/reference/createPaymentsByIdRefund) for all response shapes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.