Statuses & Lifecycles

Most Flint resources carry more than one status, and each answers a different question. An Order has five. A Return has four. Reading the wrong one, or inferring one from another, is the most common source of integration bugs on this API.

Two habits avoid nearly all of them:

  • Read the field that answers your question. payment_status says whether money arrived. fulfillment_status says whether goods left. Neither implies the other, and an order can be paid and not_fulfilled for weeks.
  • Treat status values as an open set. Flint adds values as products grow. Switch on the ones you handle and let anything else fall through to a safe default rather than an exception.

The same word means different things in different fields. paid on an Order means the balance is settled; paid on a Payout means the money reached the bank. pending appears on refunds, payouts, fulfillments, and returns with four different meanings. Always read the value alongside its field name.

Orders#

An Order is the durable record, so it carries the most projections.

FieldQuestionValues
statusIs the order still open for changes?open, closed
payment_statusHas the money arrived?unpaid, partially_paid, paid
fulfillment_statusHave the goods left?not_fulfilled, partially_fulfilled, fulfilled, canceled
refund_statusHas money gone back?none, partially_refunded, refunded
inventory_exception_statusDid stock fail after payment?paid_inventory_failed, resolved

status is narrower than people expect. It tracks whether the order is mutable, not whether it is finished. An order that was paid and fully refunded stays closed with payment_status: paid and refund_status: refunded, because money did settle before it came back. Reconstructing "was this refunded" from status alone will be wrong.

See Orders first for the model and Order activities for the history log.

Payments#

Two objects, and the difference matters. A PaymentIntent is one payment leg. A payment attempt is one execution across the legs you selected.

ResourceValues
PaymentIntent.statusrequires_payment_method, requires_confirmation, requires_action, processing, requires_capture, succeeded, canceled, expired
payment attempt statusprocessing, requires_action, requires_retry, finalizing, requires_capture, partially_succeeded, succeeded, failed, canceled, expired

Three of these are worth knowing before you write the branch:

  • requires_capture means an authorization succeeded and is holding funds. Nothing settles until you capture it, and the authorization expires if you do not.
  • requires_retry is an unknown outcome, not a decline. The provider timed out and the charge may have gone through, so resume the same attempt rather than starting a new one.
  • finalizing is neither running nor finished. The money outcome is settled and Flint is still applying it to the order. Starting a new payment here charges the buyer twice.

On an attempt, prefer is_resumable over pattern-matching the status. Handling declines covers the full recovery model.

Refunds#

pending, in_transit, requires_action, succeeded, partially_succeeded, failed, canceled.

A refund is asynchronous. A 201 means Flint accepted it, not that the buyer has their money. Wait for refund.updated reaching a terminal value, and expect partially_succeeded on multi-payment orders where some legs settle and others do not. See Refunds.

Returns#

A Return keeps four statuses precisely because the decision, the goods, and the money move at their own pace.

FieldQuestion
decision_statusDid we say yes?
merchandise_statusDid the goods come back?
resolution_statusDid the buyer get their value?
statusIs the whole thing finished?

completion_blockers is the authoritative list of what is still owed, and an empty array is the real "done" signal. Act on return.completed, never on a refund succeeding: a resolution can settle while merchandise work is still open. Full value sets are in the Returns reference, and the model is in Returns.

Invoices and subscriptions#

ResourceValues
Invoice.statusdraft, open, partially_paid, paid, void
Invoice.refund_statusnone, partially_refunded, refunded
Subscription.statustrialing, active, paused, past_due, canceled, incomplete

past_due is a billing state, not an entitlement decision. Whether a past-due subscriber keeps access is your policy, so read the status and decide rather than assuming Flint has cut them off. incomplete means the first payment never completed, so the subscription never really started.

See Invoicing and Subscription billing.

Checkout sessions#

open, paid, partially_paid, expired, closed, invalidated.

invalidated is distinct from expired: the session was made unusable by a change to the underlying order, not by the clock running out. Both are terminal, and neither means the buyer paid.

Fulfillment, shipments, and packages#

Fulfillment statuses vary by type, which is why the set is wide: pending, scheduled, accepted, on_hold, in_progress, preparing, picked, packed, ready, dispatched, completed, no_show, canceled, failed.

Packages run created, packed, shipped, in_transit, out_for_delivery, delivery_attempted, delivered, exception, returned, and voided. Shipments expose the same states as a read-only aggregate of their packages: exceptions and voids dominate, then the least advanced active package determines the shipment state.

A shipment also carries a direction. A return leg uses direction: return and is how merchandise comes back, which is why return tracking lives here rather than on the Return.

Disputes and payouts#

Dispute.status runs warning_needs_response, warning_under_review, warning_closed, needs_response, under_review, won, lost, prevented. The warning_* values are early fraud warnings, which are not yet disputes and may never become one.

Payout.status is pending, in_transit, paid, failed, canceled, with a separate reversal_status of none, reversing, reversed. A paid payout can still be reversed, so read both.

Which values are final#

Anything not listed here can still change, so keep polling or keep listening.

ResourceFinal values
Order statusclosed
PaymentIntentsucceeded, canceled, expired
Payment attemptsucceeded, partially_succeeded, failed, canceled, expired, requires_capture
Refundsucceeded, partially_succeeded, failed, canceled
Invoicepaid, void
Subscriptioncanceled
Checkout sessionpaid, expired, closed, invalidated
Fulfillmentcompleted, canceled, failed, no_show
Shipmentdelivered, returned, voided
Disputewon, lost, prevented, warning_closed
Payoutpaid, failed, canceled
Returncompleted, declined, canceled

Two of these are final but not finished, and both cost real money if you treat them as done:

  • requires_capture is terminal for the attempt and pending for the money. Funds are held and expire uncaptured.
  • Order closed stops mutation, not activity. Refunds, disputes, and returns all continue against a closed order.
  • Return completed can be reopened to record late compensating facts. Nothing else in the table reverses.

Reading statuses safely#

  • The resource is the truth, events are the notification. Webhooks are at-least-once and can arrive out of order. When the next action depends on current state, read the resource.
  • Compare revisions, not arrival order. Resources that support optimistic concurrency carry a revision. A payload with an older revision than you already applied is stale.
  • Do not switch exhaustively. New status values are additive and non-breaking under /v1. A default branch is required, not defensive.
  • Money statuses are not permissions. past_due, requires_capture, and partially_paid describe what happened to the money. What access that buys is your product decision.

Next steps#

Rate this doc