With hosted checkout you skip the payment form. Your server describes the cart in one call, and the answer is a Checkout Session: a page on Fluveo's domain with its own address. You send the buyer to that address. The page shows the cart and its total, collects the payment, and returns the buyer to your shop. No card number ever reaches your servers. Each session pays through a PaymentIntent, so Accept a payment holds for it too.
Pick it when a page you host is enough and you would rather not build a form. Checkout platforms can point their buyers at this page as well.
Create a session and redirect the buyer#
Create the Checkout Session#
Give each cart line a name, a unit price and a quantity. Prices are integers in the currency's smallest unit, so
2000is $20.00. Addsuccess_url, where the buyer lands after paying, andcancel_url, where the buyer goes on giving up. Fluveo writes the session's id in place of{CHECKOUT_SESSION_ID}insuccess_url.Create a Checkout Sessioncurl https://api.fluveo.com/v1/checkout/sessions \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: cart-88-session" \ -d mode=payment \ -d "line_items[0][price_data][currency]=usd" \ -d "line_items[0][price_data][unit_amount]=2000" \ -d "line_items[0][price_data][product_data][name]=Blue T-shirt" \ -d "line_items[0][quantity]=1" \ --data-urlencode "success_url=https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}" \ --data-urlencode "cancel_url=https://shop.example.com/cart" \ -d client_reference_id=cart-88const response = await fetch("https://api.fluveo.com/v1/checkout/sessions", { method: "POST", headers: { Authorization: "Bearer sk_test_...", "Idempotency-Key": "cart-88-session", }, body: new URLSearchParams({ mode: "payment", "line_items[0][price_data][currency]": "usd", "line_items[0][price_data][unit_amount]": "2000", "line_items[0][price_data][product_data][name]": "Blue T-shirt", "line_items[0][quantity]": "1", success_url: "https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}", cancel_url: "https://shop.example.com/cart", client_reference_id: "cart-88", }), }); const session = await response.json(); console.log(session.url);import requests response = requests.post( "https://api.fluveo.com/v1/checkout/sessions", headers={ "Authorization": "Bearer sk_test_...", "Idempotency-Key": "cart-88-session", }, data={ "mode": "payment", "line_items[0][price_data][currency]": "usd", "line_items[0][price_data][unit_amount]": 2000, "line_items[0][price_data][product_data][name]": "Blue T-shirt", "line_items[0][quantity]": 1, "success_url": "https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}", "cancel_url": "https://shop.example.com/cart", "client_reference_id": "cart-88", }, ) session = response.json() print(session["url"])<?php $ch = curl_init("https://api.fluveo.com/v1/checkout/sessions"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer sk_test_...", "Idempotency-Key: cart-88-session", ], CURLOPT_POSTFIELDS => http_build_query([ "mode" => "payment", "line_items" => [[ "price_data" => [ "currency" => "usd", "unit_amount" => 2000, "product_data" => ["name" => "Blue T-shirt"], ], "quantity" => 1, ]], "success_url" => "https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}", "cancel_url" => "https://shop.example.com/cart", "client_reference_id" => "cart-88", ]), ]); $session = json_decode(curl_exec($ch), true); echo $session["url"];Send the buyer to the url#
The session's id starts with
cs_test_. It isopenandunpaid, andurlis its page. Redirect the buyer's browser there.payment_intentisnulluntil the buyer presses pay on the page; from then on it holds thepi_id.Response (trimmed){ "id": "cs_test_...", "object": "checkout.session", "mode": "payment", "status": "open", "payment_status": "unpaid", "amount_total": 2000, "currency": "usd", "payment_intent": null, "url": "https://.../c/cs_test_...", "expires_at": 1767312000 }Fulfil from the webhook#
Ship the order when your endpoint gets
checkout.session.completed, withpayment_statuspaid. Fluveo sendspayment_intent.succeededbeside it. Treat the visit tosuccess_urlas a thank-you page only. It proves nothing: the address can be typed by hand, and the buyer may never get back to it.checkout.session.completed (trimmed){ "id": "evt_...", "type": "checkout.session.completed", "data": { "object": { "id": "cs_test_...", "object": "checkout.session", "status": "complete", "payment_status": "paid", "client_reference_id": "cart-88" } } }
paymentcharges the cart.setupsaves the buyer's card with a SetupIntent and charges nothing: see Save a card without charging.Required in
paymentmode, refused insetupmode. The cart, one entry per line:line_items[0][...],line_items[1][...]. At most 100 lines, every line in one currency.2 child fields
The price of one unit.
3 child fields
A currency your account accepts, lowercase, such as
usd.One unit's price, as an integer in the currency's smallest unit.
1 child field
The line's name on the page.
Units of this line. Defaults to 1.
The buyer's next stop after paying.
{CHECKOUT_SESSION_ID}becomes the session's id.The buyer's next stop on leaving the page without paying.
Your id for this cart or order, returned on the session and its events.
Fills the email box for the buyer.
A
cus_id of your store. Send this orcustomer_email, not both.Unix time at which the page stops taking payment, 30 minutes from now or later. Without it, the page lasts 24 hours.
Key-value pairs of yours, kept on the session.
Settings for the session's PaymentIntent, read as
POST /v1/payment_intentsreads them:capture_method,description,metadata,receipt_email,setup_future_usage,shipping,statement_descriptorandstatement_descriptor_suffix. Asetupsession refuses it.descriptionandmetadatafor the SetupIntent of asetupsession. Apaymentsession refuses it.What the page lets the buyer pay with. Without it, the page offers the methods your account has on in its payment method configuration. See Payment methods on the page.
payment_method_options[pix][expires_after_seconds](60 to 86400) andpayment_method_options[boleto][expires_after_days](0 to 60). Send each one with its method inpayment_method_types.
Payment methods on the page#
payment_method_types sets what the page shows. Without it, the page shows every method your account accepts and has on in its payment method configuration, less the ones the session cannot take: payment_intent_data[capture_method]=manual leaves out pix and boleto, and payment_intent_data[setup_future_usage]=on_session leaves out pix. The session call and its fields are the same whatever the method.
payment_method_types | On the page | Options |
|---|---|---|
card | A card form | none |
pix | A Pix QR code and a code to copy | payment_method_options[pix][expires_after_seconds] |
boleto | A Boleto with its line and PDF | payment_method_options[boleto][expires_after_days] |
Send several methods together and the buyer picks one. Each method charges in the currencies listed in Payment methods, and a method your account does not accept answers a 400.
A total under the currency's minimum charge, such as $0.50 (50) in usd, answers a 400. So does a parameter the session does not take, and the error names it. Every route is in the API reference.
Save a card without charging#
A session with mode=setup charges nothing. Its page asks for the buyer's card and saves it with a SetupIntent, so you can charge that card later with a PaymentIntent. Send no line_items: a setup session refuses them. Send currency (or payment_method_types), and a customer to attach the saved card to.
curl https://api.fluveo.com/v1/checkout/sessions \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: cus-42-save-card" \
-d mode=setup \
-d currency=usd \
-d customer=cus_... \
--data-urlencode "success_url=https://shop.example.com/card-saved?session={CHECKOUT_SESSION_ID}" \
--data-urlencode "cancel_url=https://shop.example.com/account"The session names its SetupIntent in setup_intent (a seti_ id) from the start. payment_intent and amount_total are null, and payment_status stays no_payment_required for the whole life of the session. Without payment_method_types, the page offers the methods your account has on in its payment method configuration that a session can save; a Pix is never saved.
status | payment_status | Meaning |
|---|---|---|
open | no_payment_required | The page waits for the buyer's card. |
complete | no_payment_required | The card is saved: the SetupIntent is succeeded and its payment_method is attached to the Customer. |
expired | no_payment_required | Closed with nothing saved. The SetupIntent is canceled. |
When the buyer saves the card, your endpoint gets setup_intent.succeeded and checkout.session.completed, with payment_status no_payment_required. Read the saved pm_ from the SetupIntent's payment_method with GET /v1/setup_intents/{id}.
Session statuses#
status | payment_status | Meaning |
|---|---|---|
open | unpaid | The page waits for the buyer: nothing paid yet, a card attempt running, or a Pix QR not yet paid. |
complete | paid | Paid. The PaymentIntent is succeeded. |
complete | unpaid | The card is authorized and waits for your capture (payment_intent_data[capture_method]=manual), or a Boleto is issued and not yet paid. |
expired | unpaid | Closed unpaid, by expires_at or by you. Its PaymentIntent is canceled. |
Other calls#
GET /v1/checkout/sessions/{id}returns one session. Yoursuccess_urlpage can call it to pick its message.GET /v1/checkout/sessionsreturns sessions, newest first. Narrow it withstatus(open,complete,expired),payment_intent,customerorcreated.GET /v1/checkout/sessions/{id}/line_itemsreturns the cart lines.POST /v1/checkout/sessions/{id}updatesmetadataand nothing else.POST /v1/checkout/sessions/{id}/expirecloses an open session now, say after the cart changed. Its PaymentIntent is canceled andcheckout.session.expiredgoes out. A paid session refuses it.
curl -X POST https://api.fluveo.com/v1/checkout/sessions/cs_test_.../expire \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: cart-88-expire"Events#
| Event | When |
|---|---|
checkout.session.completed | The buyer paid, or a setup session saved the card. With manual capture, the card was authorized; with a Boleto, it was issued: payment_status is then unpaid. A Pix sends it once paid. |
checkout.session.async_payment_succeeded | An issued Boleto was paid. |
checkout.session.async_payment_failed | An issued Boleto expired unpaid. |
checkout.session.expired | The session closed unpaid, at expires_at or by your call. |
The session's PaymentIntent sends its own events too. They are listed on Accept a payment.
Test it#
- Create a session with a
sk_test_key, as above, and open itsurlin a browser. - Pay with
4000 0000 0000 0002, any future expiry and any CVC. The page shows the decline and stays open. - Pay with
4242 4242 4242 4242. The browser goes to yoursuccess_url. GETthe session.statusiscomplete,payment_statusispaid, and your endpoint gotcheckout.session.completed.- Create another session and expire it.
statusisexpired, andcheckout.session.expiredarrives. - Repeat the create with the same
Idempotency-Keyand body. The samecs_test_comes back, withIdempotent-Replayed: true.
Next#
- Accept a payment with the API alone.
- Refund a payment
- Webhooks