A partial refund sounds like a payment operation, but the hard part is almost never sending money back to the card network.
The hard part is deciding what changed in the order.
Charge-first
The processor sees a dollar amount
A buyer says: "Keep the travel fee. Refund the unused service."
await processor.refunds.create({
payment_intent: "pi_123",
amount: 1437,
})
The processor can move money, but it cannot answer:
- Which line items changed
- Whether discount allocation should move
- How tax should be adjusted
- What the order now says
Order-first
The order carries the missing context
If the order is the primary record, the refund starts from a structure that already knows:
- Line items and quantities
- Tips and discounts
- Tax and balance
- Payment history
- Current order status
{
"order_id": "ord_abc123",
"status": "paid",
"line_items": [...],
"pricing_amounts": {...},
"settlement_amounts": {...},
"refund_ids": ["ref_abc123"]
}
Partial refunds are really order questions#
Buyers rarely ask for a refund in the language a processor prefers.
They ask for:
- One product back out of a multi-product purchase
- The unused portion of a service
- The deposit back but not the travel fee
- A canceled registration while preserving a platform component
Those are decisions about order structure, not just money movement.
Why charge-first stacks get expensive around refunds#
When the charge is the main object, your team still has to answer four questions somewhere else:
- What part of the order does this amount represent?
- How should tax move after the refund?
- What does support see when the customer asks what changed?
- What state should accounting trust after the refund succeeds?
That is why partial refunds often degrade into spreadsheet logic, internal notes, and support judgment calls.
The hidden cost
A refund endpoint can return success while your actual order state is still ambiguous. That ambiguity is where support load and reconciliation work come from.
What Flint's current model proves#
Flint's public model already puts refunds in the context of the order:
- Orders are the central commerce resource for line items, tips, discounts, tax, and payment state.
- The order lifecycle includes refunded states, not just open and paid states.
- The order exposes
refund_ids, computedrefunded_money, and order activity. - The Refunds API accepts an
order_idor apayment_intent_id, plus an optionalamount_money. - Refund creation is idempotent and reason-coded.
That matters because a partial refund can be evaluated against a structured order instead of an opaque charge record.
Be precise about today's API
Flint's current public Refunds API is order-aware and amount-based. The point of this model is not that the processor magically infers intent. The point is that the refund amount can be chosen against a real order that already contains line items, discounts, tax, and payment history.
Example: refund part of an order, not just part of a charge#
The order gives your tooling a stable place to inspect what happened before choosing the refund amount.
{
"order_id": "ord_abc123",
"reason": "requested_by_customer",
"amount_money": {
"amount": 2500,
"currency": "USD"
}
}
{
"data": {
"refund_id": "ref_abc123",
"order_id": "ord_abc123",
"amount_money": {
"amount": 2500,
"currency": "USD"
},
"status": "pending",
"reason": "requested_by_customer",
"refund_method": "original_payment"
},
"request_id": "b4e7d2c9-1f5a-4b8e-a3d6-7c2f9e4b0a58"
}
That request is still simple. The difference is what surrounds it.
In a charge-first system, your app has to reconstruct the refund context from its own tables and metadata before it can safely pick 2500.
In an order-first system, your app or agent starts from the order record, sees the line items, sees the computed amounts, and makes the decision against the same object finance and support will inspect later.
What changes after the refund#
Before
Paid order
{
"order_id": "ord_abc123",
"status": "paid",
"refund_ids": [],
"pricing_amounts": {
"subtotal_money": { "amount": 5000, "currency": "USD" },
"tax_money": { "amount": 400, "currency": "USD" },
"total_money": { "amount": 5400, "currency": "USD" }
},
"settlement_amounts": {
"refunded_money": { "amount": 0, "currency": "USD" },
"amount_due_money": { "amount": 0, "currency": "USD" }
}
}
After
Refund attached to the order
{
"order_id": "ord_abc123",
"status": "partially_refunded",
"refund_ids": ["ref_abc123"],
"settlement_amounts": {
"refunded_money": { "amount": 2500, "currency": "USD" },
"amount_due_money": { "amount": 2500, "currency": "USD" }
}
}
Here amount_due_money is the order balance view after the refund. It does not mean Flint will automatically collect that amount again.
The refund is no longer an isolated processor event. It is part of the order's financial history.
Why support and reporting get better#
An order-first refund model improves more than the API call:
- Support can explain what changed without cross-referencing multiple systems.
- Reporting can tie refunded money back to the original commercial context.
- Internal tools can reason from one record instead of payment metadata plus database joins.
- AI agents get a safer object to inspect before acting.
This is why teams eventually discover that refund quality is downstream from order quality.
The practical takeaway#
If partial refunds keep turning into manual reasoning, the bottleneck is usually not the refund endpoint.
It is whether the order exists as a real first-class object with enough context to make the refund obvious.
Read the actual surfaces#
Orders API
Read the central order model behind line items, tax, discounts, and payment state.
Refunds API
See the current create, list, and retrieval surface for refunds.
Payment API
See Flint's higher-level order-first payment framing.
Commerce API
See how refunds fit into the broader commerce model.
