Accept your first payment
Create an order, send a buyer to a Flint-hosted checkout page, pay with a test card, and read the paid order back from the API. You need curl, a browser, and a test API key. There is no checkout UI to build, and no real money moves.
Before you start#
Get a test API key. Create one on the dashboard's API keys page. It starts with flint_test_ and is bound to a sandbox, so everything below runs against isolated test data. If your backend provisions merchants, API & agent onboarding creates the account and its first key instead.
Give the key three permissions: capabilities.read, commerce.orders.write, and checkouts.checkout_sessions.write. A write permission also reads, so commerce.orders.write covers fetching the order in step 4.
Keep the key on your backend. Every request goes to https://api.withflintpay.com with the key in the Authorization header. Test and live keys share that hostname, and the key decides the mode. Replace YOUR_API_KEY in each command with your key.
If you're signed in to the docs, each request below can run against your sandbox with your test key filled in, and the order ID from step 1 carries into the later requests.
Check that your sandbox can take card payments:
curl "https://api.withflintpay.com/v1/capabilities?capability=accept_card_payments" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"capability": "accept_card_payments",
"domain": "payments",
"status": "ready"
}
]
}
status: "ready" means checkout will show a card form.
A 200 response confirms the request worked, not that the sandbox can charge cards. While the capability is pending or blocked, the hosted checkout page says "No payment methods are currently available." instead of showing a card form. The response's blocked_reasons and requirements name what is still due. Complete it in the dashboard, then run the check again.
Step 1: create an order#
An order is Flint's record of a sale: what was sold, what it costs, and how much of that has been paid. Every payment settles against one. Create an order for a $25 design consultation. Amounts are integers in the currency's minor unit, so 2500 is $25.00. See Money & currency for other currencies.
curl -X POST https://api.withflintpay.com/v1/orders \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: first-payment-order-001" \
-d '{
"line_items": [{
"name": "Design consultation",
"quantity": 1,
"unit_price_money": {"amount": 2500, "currency": "USD"}
}],
"tax": {"enabled": false}
}'
The line item is defined on the order itself, so you don't need a product in your catalog first. To sell from your catalog, reference a product instead. Pass customer_id to connect the order to a customer.
tax.enabled: false turns tax off for this order, so no address is needed and the total stays $25.00 whatever your dashboard tax settings are.
{
"data": {
"order_id": "ord_1kmn0aExample",
"status": "open",
"payment_status": "unpaid",
"pricing_amounts": {
"total_money": {"amount": 2500, "currency": "USD"}
},
"settlement_amounts": {
"paid_money": {"amount": 0, "currency": "USD"},
"outstanding_money": {"amount": 2500, "currency": "USD"}
}
}
}
The order is open with $25.00 outstanding, and nothing has been charged. Save data.order_id and use it in place of ord_1kmn0aExample in the commands that follow.
Safe to retry
Both POST requests carry an Idempotency-Key. If a request times out, send it again with the same key and body and Flint returns the original result instead of creating a second object. To run the walkthrough again with a new order, change both keys, for example from 001 to 002. Keys are honored for 24 hours. See Idempotency.
Step 2: create a checkout session#
A checkout session is one hosted payment page for one order and one buyer. It collects the order's outstanding balance, so you don't send the amount again.
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: first-payment-checkout-001" \
-d '{
"order_id": "ord_1kmn0aExample",
"tip": {"enabled": false},
"redirects": {
"success_redirect_url": "https://example.com/thanks",
"cancel_redirect_url": "https://example.com/checkout"
}
}'
Tipping is on by default for new accounts. tip.enabled: false keeps the tip prompt off this checkout so the total stays $25.00. The redirect URLs are placeholders; in your app they point at your own pages.
{
"data": {
"checkout_session": {
"checkout_session_id": "cs_1kmn0aExample",
"status": "open",
"surface": "hosted",
"order_id": "ord_1kmn0aExample",
"expires_at": "2026-09-05T17:04:05Z"
},
"checkout_access": {
"checkout_auth_token": "ckat_v1...",
"hosted_url": "https://checkout.withflintpay.com/checkout/cs_1kmn0aExample#checkout_token=..."
}
}
}
Open data.checkout_access.hosted_url from your response in a browser. Use the URL exactly as returned. The #checkout_token fragment is what admits the buyer, so don't rebuild the URL from the session ID or write it to logs. A session expires 24 hours after creation unless you set expiration.expires_in_seconds.
Step 3: pay with a test card#
The checkout page shows a test-mode banner and a $25.00 total. Fill in the buyer details it asks for and pay with:
Flint processes card payments on Stripe, so Stripe's test cards work here and you don't need a Stripe account. Cards that decline or require 3D Secure are listed in Testing.
After the payment succeeds, checkout redirects to success_redirect_url with csId and orderId query parameters. With the placeholder URL you land on example.com, which knows nothing about your order. The next step confirms the payment from the API.
Step 4: verify the payment#
Fetch the order you created in step 1:
curl https://api.withflintpay.com/v1/orders/ord_1kmn0aExample \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": {
"order_id": "ord_1kmn0aExample",
"status": "closed",
"payment_status": "paid",
"settlement_amounts": {
"paid_money": {"amount": 2500, "currency": "USD"},
"refunded_money": {"amount": 0, "currency": "USD"},
"outstanding_money": {"amount": 0, "currency": "USD"}
},
"checkout_session_ids": ["cs_1kmn0aExample"]
}
}
payment_status: "paid" with outstanding_money.amount at 0 is the confirmation. paid_money records the $25.00 collected, and the order closed because nothing is left to collect. An order can also be closed without payment, so check payment_status rather than status.
If you paid with a declined card or left checkout, the order is still open and unpaid with $25.00 outstanding, and the checkout session is still open. The same URL can still collect the payment, so don't create a second order to retry.
Take it into your app#
In production the same four calls run from your backend. What changes:
- Your backend creates the order and the session, then sends the buyer to
hosted_url. A server-rendered app responds with an HTTP redirect. A single-page app navigates the browser. The API key never reaches the browser. - Store the Flint
order_idwith your own record of the purchase when you create it. When the buyer returns, look up the order through your record. TheorderIdin the redirect URL is for the buyer's return page, not something to fulfill from. - Fulfill from the
order.paidwebhook, not from the redirect. A buyer can close the tab before the redirect fires. Subscribe toorder.paid, verify the signature, fetch the order, and fulfill. Flint retries a delivery until your endpoint acknowledges it, so make fulfillment safe to repeat. See Webhooks. - Live mode is the same code with a live key. Same hostname, same requests. Going live covers account verification and the cutover.
Next steps#
- Checkout sessions: buyer details, payment options, expiration, replacing a session, and appearance.
- Testing: declines, 3D Secure, refunds, and webhook deliveries in your sandbox.
- Embedded payments with Stripe Elements: collect the card in your own UI instead of redirecting.
- SDKs and CLI: the same flow with
@flintpay/node,flintpay/flint, or from your terminal. - Error handling: read a failed response and decide whether to retry.
