Why Flint Is Orders-First

Most payment APIs start with the charge: you pass an amount, collect a card, and everything your business actually knows about the purchase lives somewhere else. Flint starts one level higher. The central object is the order: the durable record of what is being bought, what adjustments were applied, what is still owed, and what happened after payment.

Checkout sessions, payment intents, invoices, and payment links are collection surfaces attached to that record. They answer "how is this getting paid right now?" The order answers "what is owed, and where did the money end up?"

This page explains the model, shows it working in a handful of API calls, and is honest about when you should skip it.

The Model at a Glance#

text
                 ┌───────────────────────────────┐
                 │             ORDER             │
                 │  the durable commerce record  │
                 │                               │
                 │  line items · discounts       │
                 │  tips · tax · metadata        │
                 │  pricing_amounts (owed)       │
                 │  settlement_amounts (paid)    │
                 └───────────────┬───────────────┘
                                 │  collected through
           ┌─────────────────────┼─────────────────────┐
           ▼                     ▼                     ▼
 ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
 │ checkout session │  │  payment intent  │  │     invoice      │
 │ Flint-hosted     │  │ your own UI with │  │ receivable,      │
 │ payment page     │  │ Stripe Elements  │  │ billed later     │
 └────────┬─────────┘  └────────┬─────────┘  └────────┬─────────┘
          │                     │                     │
          └─────────────────────┼─────────────────────┘
                                ▼
                refunds · webhooks · reports · support
                 all join back to the same order_id

Payment links sit one level above this diagram: a link is a reusable public page, and each buyer who opens it gets a fresh order created for that visit.

Every object answers one question, and the boundaries stay clean:

ObjectThe question it answersLifetimeReference
OrderWhat does the buyer owe, and what happened to it?Durable recordOrders API
Checkout sessionHow is this buyer paying right now?One hosted attemptCheckout Sessions API
Payment intentDid this specific attempt to move money succeed?One payment attemptPayments API
InvoiceHow do we bill and track this balance over time?Long-lived receivableInvoices API
Payment linkWhat reusable page starts new purchases?Long-lived templatePayment Links API

One Order, Any Collection Path#

The fastest way to understand the model is to watch one order flow through it. Amounts are integers in the currency's minor unit, so 45000 is $450.00.

Create the Order Once#

The order carries the full commercial state: line items, discounts, and your own metadata.

Bash
curl -X POST https://api.withflintpay.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: order-workshop-001" \
  -d '{
    "line_items": [{
      "name": "Design workshop seat",
      "quantity": 2,
      "unit_price_money": {"amount": 45000, "currency": "USD"}
    }],
    "discounts": [{
      "manual": {
        "name": "Early bird",
        "amount_money": {"amount": 5000, "currency": "USD"}
      }
    }],
    "metadata": {"campaign": "spring-workshops"}
  }'
JSON
{
  "data": {
    "order_id": "ord_1kmn0aExample",
    "status": "open",
    "pricing_amounts": {
      "subtotal_money": {"amount": 90000, "currency": "USD"},
      "discount_money": {"amount": 5000, "currency": "USD"},
      "total_money": {"amount": 85000, "currency": "USD"}
    },
    "settlement_amounts": {
      "paid_money": {"amount": 0, "currency": "USD"},
      "amount_due_money": {"amount": 85000, "currency": "USD"}
    },
    "line_items": [...],
    "metadata": {"campaign": "spring-workshops"}
  }
}

Notice the split in the response. pricing_amounts is the agreement: what was sold and for how much. settlement_amounts is reality: what has actually been collected so far. Nothing is paid yet, so amount_due_money equals the total.

Shape It While It Is Open#

An open order is mutable. Add line items, apply discounts, add fees, request a tip, or update tax, and the server recalculates every total on each change. You never do money math client-side.

Bash
curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/line-items \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: order-workshop-001-addon" \
  -d '{
    "line_items": [{
      "name": "Workshop materials kit",
      "quantity": 2,
      "unit_price_money": {"amount": 3500, "currency": "USD"}
    }]
  }'
JSON
{
  "data": {
    "order_id": "ord_1kmn0aExample",
    "status": "open",
    "pricing_amounts": {
      "subtotal_money": {"amount": 97000, "currency": "USD"},
      "discount_money": {"amount": 5000, "currency": "USD"},
      "total_money": {"amount": 92000, "currency": "USD"}
    },
    "settlement_amounts": {
      "amount_due_money": {"amount": 92000, "currency": "USD"}
    }
  }
}

The mutation endpoints all hang off the order: POST /v1/orders/{order_id}/line-items, /discounts, /charges, /requested-tip, and PATCH /v1/orders/{order_id}/tax. See the Orders API for the full set.

Collect It However Fits#

Here is the payoff. The same ord_1kmn0aExample can be collected through any surface, and the order does not change shape to accommodate the choice.

Create a checkout session pointing at the order and send the buyer to the returned URL. Flint hosts the payment page.

Bash
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: checkout-workshop-001" \
  -d '{
    "order_id": "ord_1kmn0aExample",
    "redirects": {
      "success_redirect_url": "https://example.com/thanks",
      "cancel_redirect_url": "https://example.com/cart"
    }
  }'
JSON
{
  "data": {
    "checkout_session_id": "cs_1kmn0aExample",
    "status": "open",
    "order_id": "ord_1kmn0aExample",
    "url": "https://checkout.withflintpay.com/checkout/cs_1kmn0aExample#checkout_token=..."
  }
}

In test mode, pay with card 4242 4242 4242 4242, any future expiry, any CVC. The full walkthrough is Accept Your First Payment.

These paths are alternatives for the same balance, so pick one per order. An open checkout session holds the order while the buyer pays; close it before switching paths. And once an order is invoiced, collect through the invoice (its hosted link or manual payments), not through a new generic checkout session.

Verify the Same Way, Whatever the Path#

Collection surfaces come and go; the order is where the outcome lands. Whichever path you chose, verification is the same read.

Bash
curl https://api.withflintpay.com/v1/orders/ord_1kmn0aExample \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "data": {
    "order_id": "ord_1kmn0aExample",
    "status": "paid",
    "pricing_amounts": {
      "total_money": {"amount": 92000, "currency": "USD"}
    },
    "settlement_amounts": {
      "paid_money": {"amount": 92000, "currency": "USD"},
      "refunded_money": {"amount": 0, "currency": "USD"},
      "amount_due_money": {"amount": 0, "currency": "USD"}
    },
    "checkout_session_ids": ["cs_1kmn0aExample"],
    "payment_intent_ids": ["pi_3Qx7Yt9rW2"],
    "refund_ids": [],
    "metadata": {"campaign": "spring-workshops"}
  }
}

Read this response as the model in miniature:

  • status runs open to paid, then partially_refunded or refunded if money goes back; closed covers orders canceled before payment. See Statuses & Lifecycles.
  • settlement_amounts answers the money questions directly: paid_money collected, refunded_money returned, amount_due_money still outstanding.
  • checkout_session_ids, payment_intent_ids, and refund_ids are the join points. Every collection attempt and every money event that ever touched this obligation is reachable from here.
  • metadata set at creation survived checkout untouched, so your correlation IDs come back out where reporting needs them.

The same durability applies to notifications: webhooks like order.payment_succeeded reference the order, so fulfillment logic is identical no matter which surface collected the money. See Webhooks.

What This Buys You#

One Definition of What Is Owed#

Pricing state lives in exactly one place. Your cart, your checkout page, and your invoice can disagree only if you rebuild pricing outside the order, and the model removes the reason to. Totals recalculate server-side on every mutation, so there is no client-side money math to drift.

Collection Can Fail, Retry, or Switch Paths#

A buyer abandons hosted checkout. A card declines. Sales agrees to net-30 after the cart was already built. In a charge-first API each of these means reconstructing purchase state in a new object; here the order survives every attempt. Close a session and open another, create a fresh payment intent, or convert the open balance to an invoice: the commerce record is never rebuilt, so it cannot silently change between attempts.

Every Money Event Has a Join Point#

"What did this charge pay for?" and "which order does this refund belong to?" are lookups, not investigations. Refunds reference the order, orders link every payment intent and checkout session they touched, and external_reference_id joins the order to your own system's IDs. Attach metadata once at creation and it flows through to reconciliation and support tooling.

Pay Now Can Become Pay Later#

An obligation often outlives its first collection plan: checkout today becomes an invoice tomorrow, or a deposit becomes a balance due. Because both surfaces attach to the same order, that transition is an API call, not a data migration.

The Shortcuts Are Still Orders#

Orders-first does not mean every integration must call POST /v1/orders before anything else. Flint has intentional shortcuts, and all of them create the order for you rather than avoiding it. There is always an order underneath.

Quick Pay#

Want hosted checkout without building the order yourself? Pass quick_pay_item and Flint creates the backing order internally.

Bash
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: checkout-quickpay-001" \
  -d '{
    "quick_pay_item": {
      "name": "Consultation deposit",
      "amount_money": {"amount": 15000, "currency": "USD"}
    },
    "redirects": {"success_redirect_url": "https://example.com/thanks"}
  }'

The create response includes the generated order_id. Keep it: refunds, reporting, and support for this payment all flow through that order. Invoices offer the same shortcut via quick_pay on invoice create.

A payment link is a reusable template, not one permanent order. Each buyer visit resolves into a fresh checkout flow with its own order, which is why links are right for one public URL shared widely, and wrong for a specific customer's balance.

Subscriptions#

Subscription signup is plan-driven rather than balance-driven, so you start from a plan_id instead of an order. Each billing cycle still produces an order, keeping recurring revenue on the same reconciliation rails as one-time payments. See Subscription Billing.

When You Do Not Need an Order#

Orders-first is the default, not a rule. If your application already owns the commerce record and you only need money movement, create a payment intent directly from an amount:

Bash
curl -X POST https://api.withflintpay.com/v1/payment-intents \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: pi-standalone-001" \
  -d '{
    "amount_money": {"amount": 5000, "currency": "USD"}
  }'

This is the right tool when you want a minimal charge flow and Flint is not the system of record for what was sold. Be clear about what you are opting out of: no itemized commerce state in Flint, weaker reuse across checkout and invoicing, and your application carries the burden of keeping purchase context attached to the payment.

The rule of thumb: if Flint owns the commerce record, create the order first. If you only need money movement, a direct payment intent is the honest choice. See the Payments API.

Common Mistakes#

Treating checkout as the source of truth. Checkout is a collection surface. If you find yourself reconstructing what was sold from session payloads or redirect URLs, read the order instead; that is what it is for.

Using payment links for customer-specific balances. A link is a reusable template, not a receivable. If the amount belongs to one identified customer, start with an order or an invoice. See Payment Links vs Checkout Sessions vs Invoices.

Rebuilding pricing in multiple places. If your order says one total and your checkout or invoice setup says another, you have created a reconciliation problem on purpose. Define the commercial state once and let collection surfaces refer to it.

Assuming orders-first is only about hosted checkout. The durable win is downstream: refunds, disputes, reporting, and support all resolve against the same object, whichever surface collected the money.

Next Steps#

Rate this doc