Return resolutions

A resolution is what the buyer gets. Approving a Return says merchandise may come back; a resolution says what happens in exchange for it.

Five kinds exist. Four of them are outcomes you choose up front:

TypeWhat the buyer gets
refundMoney back on the original payment
exchangeDifferent merchandise, with the difference settled either way
replacementThe same merchandise again, with no money moving
no_monetary_actionNothing. Used when goods come back but no value is owed

The fifth, correction, exists to fix one of the other four after it has already settled. It has its own section below.

Store credit is reserved and not published yet. If refunding the original payer is not acceptable, decline the monetary resolution rather than working around it.

Preview, create, confirm#

Three steps, and it matters that they are three.

Preview prices an option without touching anything. No approval, no reservation, no effect. Use it to show a buyer what they would get before they commit, or to check your own arithmetic before moving money.

Bash
curl -X POST https://api.withflintpay.com/v1/return-resolution-previews \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "return_id": "ret_1kmn0aExample",
    "expected_return_revision": 3,
    "resolution_type": "refund",
    "line_items": [
      { "return_line_item_id": "retli_1kmn0aExample", "quantity": 1 }
    ]
  }'

Every preview names the based_on_return_revision it was calculated against. A preview from before the Return changed is recognisably stale rather than quietly wrong.

Create proposes the resolution and reserves line value, so two resolutions cannot both claim the same approved quantity.

Bash
curl -X POST https://api.withflintpay.com/v1/returns/ret_1kmn0aExample/resolutions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: resolution-001" \
  -d '{
    "expected_return_revision": 3,
    "resolution_type": "refund",
    "line_items": [
      { "return_line_item_id": "retli_1kmn0aExample", "quantity": 1 }
    ]
  }'

Confirm freezes the economics and starts the effects. Before this call the resolution is editable and nothing has moved. After it, the amounts are settled facts.

Bash
curl -X POST https://api.withflintpay.com/v1/return-resolutions/retres_1kmn0aExample/confirm \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: confirm-resolution-001" \
  -d '{ "expected_return_resolution_revision": 1 }'

Confirmed is not the same as paid#

A confirmed resolution can sit in pending for a long time, and that is usually correct. execution_blockers says what it is waiting for.

BlockerCleared by
handoff_pendingThe buyer hands the parcel to a carrier
receipt_pendingThe warehouse records the merchandise arriving
inspection_pendingAn inspection and its acceptance decision
manual_release_pendingSomeone calling /release
buyer_payment_pendingThe buyer paying an exchange balance
refund_pendingThe refund settling
replacement_order_pendingThe replacement Order being created
fulfillment_pendingThe replacement shipping
credit_effect_pendingA linked credit effect completing

The first three exist because of refund_timing, which the decision sets per line. after_handoff pays on drop-off, after_receipt on arrival, after_inspection once the goods are checked. That single field is how you avoid refunding for goods that never show up.

A resolution's own lifecycle runs proposed to pending to partially_fulfilled to fulfilled, with requires_action, failed, and canceled as the ways out. requires_action means someone has to do something, usually the buyer paying a balance.

Fees#

Restocking and return shipping charges are not separate objects. They are adjustments on the resolution, which is what keeps them in the same arithmetic as the refund.

Adjustment typeTypical effect
restocking_feeDeduction
return_shipping_feeDeduction
other_feeDeduction
goodwill_creditCredit
price_correctionEither

A policy proposes its fees automatically; you can also add them by hand. Deductions reduce what the buyer receives and appear in the Return's financial_summary as deduction_money, separately from returned_total_money and refunded_money. Nothing is netted invisibly.

When the buyer owes money#

An exchange for something more expensive leaves a balance. Flint links a Return-scoped PaymentIntent to the resolution and holds buyer_payment_pending until it succeeds.

Bash
curl -X POST https://api.withflintpay.com/v1/return-resolutions/retres_1kmn0aExample/checkout-session \
  -H "Authorization: Bearer YOUR_API_KEY"

The 201 uses the standard checkout launch envelope: data.checkout_session plus data.hosted_checkout with its URL and bounded authorization token. From there it is the ordinary payment collection flow, unchanged. Send no body, and do not copy processor secrets onto the Return.

If that payment fails terminally, the resolution becomes failed and exposes retry:

Bash
curl -X POST https://api.withflintpay.com/v1/return-resolutions/retres_1kmn0aExample/retry \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: retry-resolution-001" \
  -d '{
    "expected_return_resolution_revision": 5,
    "reason": "payment_method_updated"
  }'

Retrying starts a new attempt. Earlier PaymentIntent IDs stay on payment_intent_ids, and a late event from an abandoned attempt cannot settle the new one. Launch /checkout-session again for the fresh attempt.

Fixing one after it settled#

Confirmed money movements are never edited backward. To change an outcome that already happened, reopen the Return and add a correction resolution that names the one it corrects.

Bash
curl -X POST https://api.withflintpay.com/v1/returns/ret_1kmn0aExample/resolutions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: correction-001" \
  -d '{
    "expected_return_revision": 12,
    "resolution_type": "correction",
    "corrects_return_resolution_id": "retres_1kmn0aOriginal",
    "adjustments": [
      {
        "adjustment_type": "price_correction",
        "value_effect": "credit",
        "status": "applied",
        "assessed_amount_money": { "amount": 500, "currency": "USD" },
        "applied_amount_money": { "amount": 500, "currency": "USD" },
        "reason": "manual_correction"
      }
    ]
  }'

A correction is adjustments only. Sending line_items, replacement merchandise, or pricing_basis is rejected, because a correction adjusts value rather than re-deciding what came back.

A net credit creates an ordinary linked Refund once confirmed. A deduction can reduce that credit to zero, but a correction that would leave the buyer owing money is rejected today. Confirm the correction, wait for its effect to succeed, then complete the Return again.

Retrieve the history with GET /v1/return-resolutions?corrects_return_resolution_id=retres_1kmn0aOriginal.

How this lands elsewhere#

A resolution that refunds creates a real Refund on the same order, drawn from the same refundable balance, and returned by the ordinary refund endpoints. It carries return_id and return_resolution_id; refunds you create directly carry neither. Both are filters on GET /v1/refunds.

The Refund's own reason is derived from the buyer's return reason rather than copied: defective becomes defective_product, arrived_late becomes arrived_too_late, changed_mind becomes customer_changed_mind, and so on. Reasons you defined yourself have no equivalent and become other, as does a refund covering lines that gave different reasons. When you need the buyer's exact wording, read the Return line, not the Refund.

Exchanges and replacements create a real Order, linked as replacement_order_id, which fulfils and ships like any other.

Next steps#

Rate this doc