Why Flint is orders-first

A charge-first payment API asks you for an amount and a card. What was sold, which discount applied, and how much is still owed all live in your database, and the charge id is the only link back to them. Flint puts that record inside the API instead. You create an order, the order holds the line items and the running balance, and the money is collected against it.

Checkout sessions, payment intents, invoices, and payment links are the ways money reaches an order. They differ in who starts the payment and when it is due. The order does not change shape depending on which one you pick.

What each object records#

Text
        order  ─────────────────────────────────────────────────────
        line items, discounts, fees, tip, tax, metadata
        pricing_amounts: what is owed
        settlement_amounts: what has been collected and refunded
        ───────────────────────────────────────────────────────────
                │                    │                     │
        checkout session      payment intent           invoice
        hosted page for       one attempt in            a bill with a due
        one buyer             your own UI               date and reminders
                │                    │                     │
                └────────────────────┴─────────────────────┘
                                     │
        refunds, webhooks, reports, and support all key on order_id

A payment link is not in the diagram because it does not attach to one order. Each buyer who completes the link gets a new order of their own.

ObjectWhat it recordsHow long it livesReference
OrderWhat was sold, and how much has been paid or refundedPermanent. open while a balance is due, closed afterOrders API
Checkout sessionOne buyer's hosted payment pageOne attempt. Expires if unusedCheckout Sessions API
Payment intentOne attempt to move money, and whether it succeededOne attemptPayments API
InvoiceWho owes the balance, by when, and how much is still dueUntil paid or voidedInvoices API
Payment linkA public URL that starts a new sale for each buyerUntil you deactivate itPayment Links API

One order from creation to paid#

Follow a single order through the model. Amounts are integers in the currency's minor unit, so 24000 is $240.00.

Create the order#

Send the line items, any discounts, and the metadata you want returned later. external_reference_id holds your own id for the sale and is a filter on the order list.

cURL
curl -X POST https://api.withflintpay.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: order-print-7781" \
  -d '{
    "external_reference_id": "studio-sale-7781",
    "line_items": [{
      "name": "Framed print, 24 x 36",
      "quantity": 1,
      "unit_price_money": {"amount": 24000, "currency": "USD"}
    }],
    "discounts": [{
      "manual": {
        "name": "Newsletter subscriber",
        "amount_money": {"amount": 2000, "currency": "USD"}
      }
    }],
    "metadata": {"channel": "studio-newsletter"}
  }'
Response
{
  "data": {
    "order_id": "ord_1kmn0aExample",
    "status": "open",
    "payment_status": "unpaid",
    "external_reference_id": "studio-sale-7781",
    "pricing_amounts": {
      "subtotal_money": {"amount": 24000, "currency": "USD"},
      "discount_money": {"amount": 2000, "currency": "USD"},
      "total_money": {"amount": 22000, "currency": "USD"}
    },
    "settlement_amounts": {
      "paid_money": {"amount": 0, "currency": "USD"},
      "outstanding_money": {"amount": 22000, "currency": "USD"}
    },
    "line_items": [...],
    "metadata": {"channel": "studio-newsletter"}
  }
}

The response separates two questions. pricing_amounts is the price: what the line items add up to after discounts, fees, tip, and tax. settlement_amounts is the money: how much has arrived, how much went back out, and how much is still owed. Before any payment, outstanding_money equals total_money.

Change it while it is open#

An open order accepts changes, and Flint recomputes the totals on every one. Add line items, discounts, or fees through their own routes under the order. Set the requested tip, tax, metadata, customer, or delivery destination with PATCH /v1/orders/{order_id}. You never compute a total yourself.

cURL
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-print-7781-hardware" \
  -d '{
    "line_items": [{
      "name": "Wall mounting hardware",
      "quantity": 1,
      "unit_price_money": {"amount": 1500, "currency": "USD"}
    }]
  }'
Response
{
  "data": {
    "order_id": "ord_1kmn0aExample",
    "status": "open",
    "pricing_amounts": {
      "subtotal_money": {"amount": 25500, "currency": "USD"},
      "discount_money": {"amount": 2000, "currency": "USD"},
      "total_money": {"amount": 23500, "currency": "USD"}
    },
    "settlement_amounts": {
      "outstanding_money": {"amount": 23500, "currency": "USD"}
    }
  }
}

The Orders API lists every mutation route. Tips & fees covers tips and fees, and Sales tax covers the tax request.

Collect the balance#

The same order can be paid through any of these. Pick the one that matches who is paying and when.

Point a checkout session at the order and redirect the buyer to its checkout_session.url.

cURL
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-print-7781" \
  -d '{
    "order_id": "ord_1kmn0aExample",
    "redirects": {
      "success_redirect_url": "https://example.com/orders/7781/thanks",
      "cancel_redirect_url": "https://example.com/orders/7781"
    }
  }'
Response
{
  "data": {
    "checkout_session": {
      "checkout_session_id": "cs_1kmn0aExample",
      "status": "open",
      "order_id": "ord_1kmn0aExample",
      "url": "https://checkout.withflintpay.com/checkout/cs_1kmn0aExample#checkout_token=..."
    },
    "checkout_access": {
      "checkout_auth_token": "ckat_v1..."
    }
  }
}

In test mode, card 4242 4242 4242 4242 with any future expiry and any CVC pays the session. Testing runs this path end to end.

Warning:

One collection flow owns an order at a time. Creating a second checkout session while one is open returns CHECKOUT_SESSION_ALREADY_EXISTS, and starting a different flow, such as issuing an invoice, returns ORDER_COLLECTION_ALREADY_ACTIVE. To hand the buyer a new hosted URL, create the replacement with replace_checkout_session_id set to the current session. To move to an invoice, close the open session first. After an invoice is issued, it owns the balance: collect through POST /v1/invoices/{invoice_id}/checkout-session, not through a new order checkout session or payment intent.

Confirm the outcome on the order#

However the money arrived, the order is what you read afterward.

cURL
curl https://api.withflintpay.com/v1/orders/ord_1kmn0aExample \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "order_id": "ord_1kmn0aExample",
    "status": "closed",
    "payment_status": "paid",
    "refund_status": "none",
    "external_reference_id": "studio-sale-7781",
    "pricing_amounts": {
      "total_money": {"amount": 23500, "currency": "USD"}
    },
    "settlement_amounts": {
      "paid_money": {"amount": 23500, "currency": "USD"},
      "refunded_money": {"amount": 0, "currency": "USD"},
      "outstanding_money": {"amount": 0, "currency": "USD"}
    },
    "checkout_session_ids": ["cs_1kmn0aExample"],
    "payment_intent_ids": ["pi_1kmn0aExample"],
    "refund_ids": [],
    "metadata": {"channel": "studio-newsletter"}
  }
}
  • status is either open or closed. payment_status moves through unpaid, partially_paid, and paid. refund_status is a separate axis, so a refunded order still reads as paid. Statuses & lifecycles has the full state tables.
  • settlement_amounts holds the money facts: paid_money in, refunded_money out, and outstanding_money still due.
  • checkout_session_ids, payment_intent_ids, and refund_ids list every attempt and every money movement that touched this order.
  • metadata and external_reference_id come back exactly as you set them at creation.

Webhooks follow the same rule. The order.paid event carries the order_id, so a fulfillment handler does the same fetch whether the buyer paid on a hosted page, in your form, or against an invoice. See Webhooks.

What the order gives you#

One total, computed once#

The price lives on the order and nowhere else. Every collection surface reads it from there, so a checkout page and an invoice for the same order cannot show different numbers. The only way to get a mismatch is to compute a total outside Flint and send that instead.

Existing orders survive failed and abandoned attempts#

A buyer leaves the hosted page, a card is declined, or a customer asks to be invoiced after the order was already built. With a charge-first API, each of these means rebuilding the purchase in a new object. For a merchant-created order, the order stays open after checkout expiry. Replace the session, create another payment intent, or issue an invoice, and the line items and balance carry over untouched.

A payment link creates an order for each resolved checkout. When that checkout expires unpaid, Flint closes the order if nothing was paid or authorized, no invoice or other active collection owns it, and it is not linked to a return. The order records closed_reason: "Checkout expired before payment" and emits order.closed. Resolve the link again to create a new checkout and order. Quick pay, subscription-plan, and invoice checkout expiry leave their orders open.

Refunds and payments point back to the order#

Which order did this refund come from? What did this payment intent pay for? Both are fields on the objects involved. Refunds target an order_id, orders list their payment intents and sessions, and GET /v1/orders?external_reference_id=... finds an order from your own id. Reconciliation works from the same fields.

Pay now can turn into pay later#

A sale that started as a checkout can become an invoice, and an invoice drafted against a partly paid order bills only what is still outstanding. Both surfaces attach to the same order, so the change is one create call rather than a copy of the sale into a new object.

Shortcuts that still create an order#

You do not have to call POST /v1/orders first. Three paths skip that step, and each creates the order for you.

Quick pay#

Send a name and an amount to a checkout session with quick_pay_item. Flint creates the order behind it and returns its order_id with the session.

cURL
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-sitting-fee-042" \
  -d '{
    "quick_pay_item": {
      "name": "Portrait sitting fee",
      "amount_money": {"amount": 12500, "currency": "USD"}
    },
    "redirects": {"success_redirect_url": "https://example.com/booked"}
  }'

Store the returned order_id. Refunds and reports for this payment reference the order, not the session. Invoices have the same shortcut: pass quick_pay on invoice create instead of order_id.

A payment link is one URL that many buyers can open. Each completed checkout produces its own order with origin: "payment_link", and the link's metadata is copied onto every order it creates. Use a link for a public offer, and an order or an invoice when one named customer owes one amount.

Subscriptions#

A subscription starts from a subscription_plan_id and a saved payment method. Each billing cycle creates an order with origin: "subscription" and the subscription_id on it, so a renewal is refunded and reported the same way as a one-time sale.

When to skip the order#

If your own system already records what was sold and you only need Flint to move the money, create a payment intent with an amount and nothing else.

cURL
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-balance-topup-311" \
  -d '{
    "amount_money": {"amount": 5000, "currency": "USD"},
    "payment_options": ["card"]
  }'

An orderless intent has no line items in Flint and cannot be turned into an invoice. The purchase context stays in your system, with metadata and external_reference_id on the intent as the link back. Collect it on your own page with Stripe Elements, as Server-confirmed payments describes. A Flint-hosted checkout page always collects an order, so for a hosted page create the checkout session with quick_pay_item instead, and Flint creates a one-line order for the amount. See the Payments API.

Decide by ownership. If Flint is the record of the sale, create the order. If your application is, create the payment intent.

Common mistakes#

Reading the sale from the checkout session. The session is one attempt to collect. Line items, totals, and payment state belong to the order, and the redirect URL is a hint, not proof of payment. Fetch the order.

Using a payment link for one customer's balance. A link creates a new order per visit, so it cannot represent an amount that a specific customer already owes. Create an order or an invoice for that. Payment links vs checkout sessions vs invoices walks through the choice.

Computing totals in your own code. Sending an amount you calculated, when the order already has one, creates two prices for one sale. Change the order and let Flint recompute.

Treating orders as a hosted checkout feature. The order matters most after the payment: refunds, disputes, reports, and support all resolve against it, whichever surface collected the money.

Next steps#

Was this helpful?