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#
┌───────────────────────────────┐
│ 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:
| Object | The question it answers | Lifetime | Reference |
|---|---|---|---|
| Order | What does the buyer owe, and what happened to it? | Durable record | Orders API |
| Checkout session | How is this buyer paying right now? | One hosted attempt | Checkout Sessions API |
| Payment intent | Did this specific attempt to move money succeed? | One payment attempt | Payments API |
| Invoice | How do we bill and track this balance over time? | Long-lived receivable | Invoices API |
| Payment link | What reusable page starts new purchases? | Long-lived template | Payment 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.
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"}
}'
{
"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.
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"}
}]
}'
{
"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.
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"
}
}'
{
"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.
Create a payment intent for the order and collect the card in your own frontend with Stripe Elements. Leave out amount_money: Flint derives it from the order balance, so your frontend can never charge a stale total.
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-workshop-001" \
-d '{
"order_id": "ord_1kmn0aExample"
}'
{
"data": {
"payment_intent": {
"payment_intent_id": "pi_3Qx7Yt9rW2",
"status": "requires_payment_method",
"amount_money": {"amount": 92000, "currency": "USD"},
"order_id": "ord_1kmn0aExample"
},
"client_confirmation": {
"stripe": {
"client_secret": "pi_3Qx7Yt9rW2_secret_Kb29rWq",
"account_id": "acct_1a2b3c4d",
"publishable_key": "pk_test_..."
}
}
}
}
Send only the client_confirmation.stripe values to the browser, mount the Payment Element with them, and confirm with Stripe.js. Do not call POST /v1/payment-intents/{payment_intent_id}/confirm for an order-linked payment intent; confirmation happens on the frontend. The full walkthrough is Embedded Payments with Stripe Elements.
The balance does not have to be collected now. Draft an invoice from the order, then send it. The recipient gets an email with a hosted payment link, reminders, and a PDF, and you can record manual payments against it.
curl -X POST https://api.withflintpay.com/v1/invoices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: inv-workshop-001" \
-d '{
"order_id": "ord_1kmn0aExample",
"recipient_email": "billing@example.com",
"due_at": "2026-08-01T00:00:00Z",
"memo": "Net 30. Thank you!"
}'
{
"data": {
"invoice_id": "inv_1kmn0aExample",
"status": "draft",
"order_id": "ord_1kmn0aExample",
"amount_due_money": {"amount": 92000, "currency": "USD"}
}
}
curl -X POST https://api.withflintpay.com/v1/invoices/inv_1kmn0aExample/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: send-inv-workshop-001"
The full billing lifecycle is covered in Invoicing.
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.
curl https://api.withflintpay.com/v1/orders/ord_1kmn0aExample \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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:
statusrunsopentopaid, thenpartially_refundedorrefundedif money goes back;closedcovers orders canceled before payment. See Statuses & Lifecycles.settlement_amountsanswers the money questions directly:paid_moneycollected,refunded_moneyreturned,amount_due_moneystill outstanding.checkout_session_ids,payment_intent_ids, andrefund_idsare the join points. Every collection attempt and every money event that ever touched this obligation is reachable from here.metadataset 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.
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.
Payment Links#
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:
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#
- Accept Your First Payment: the hosted checkout path end to end in four calls.
- Embedded Payments with Stripe Elements: order-linked payment intents in your own UI.
- Invoicing: drafts, sending, reminders, and manual payments.
- Payment Links vs Checkout Sessions vs Invoices: choosing the right collection surface.
- Statuses & Lifecycles: how orders, payment intents, and invoices move between states.
- Orders API Reference: every field and mutation endpoint on the order.
