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": "closed",
  "payment_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.

How Flint models this#

Flint's public model 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 tracks refunds on their own axis (refund_status), separate from workflow state (status) and collection state (payment_status), so a refund never rewinds the order's lifecycle.
  • 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.

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": "closed",
  "payment_status": "paid",
  "refund_status": "none",
  "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" },
    "outstanding_money": { "amount": 0, "currency": "USD" }
  }
}

After

Refund attached to the order

JSON
{
  "order_id": "ord_abc123",
  "status": "closed",
  "payment_status": "paid",
  "refund_status": "partially_refunded",
  "refund_ids": ["ref_abc123"],
  "settlement_amounts": {
    "refunded_money": { "amount": 2500, "currency": "USD" },
    "outstanding_money": { "amount": 2500, "currency": "USD" }
  }
}

Here outstanding_money is the order balance view after the refund. The order remains closed, so Flint will not 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.

Orders First

Read the order-first model behind Flint's payment and refund design.

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.