Flint blog

Refund Operations

Partial Refunds Start With Order Structure

Why partial refunds are easier when the order, not the charge, is the system of record.

4 min readBy Flint Pay, Product & API Team

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."

TypeScript
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
JSON
{
  "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, computed refunded_money, and order activity.
  • The Refunds API accepts an order_id or a payment_intent_id, plus an optional amount_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.

Request
JSON
{
  "order_id": "ord_abc123",
  "reason": "requested_by_customer",
  "amount_money": {
    "amount": 2500,
    "currency": "USD"
  }
}
Response
JSON
{
  "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

JSON
{
  "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

JSON
{
  "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.

Next step

Move refund logic up to the order layer

Start from the order model, then use the refund API against a record that already contains the financial context support and finance need.