Collect a payment (cart)
Being rolled out. The interface described here is the one of the next release: it may still change, and may not be enabled on your tenant yet.
What it is for
For a one-off purchase, your application sends a cart: the customer, the lines, the prices and the VAT. The platform returns a payment link to a page in your colours. Your customer pays by card (Stripe) or PayPal; the invoice is issued automatically.
There is no catalogue: each cart carries its own lines.
Prerequisites
- A payment account that is ready (
ready: true), withone_timeinbillingModes. - A token of the application declared in your account, with the
cartsscope.
1. Create the cart
curl -s -X POST https://billing-api.dev.stackngo.fr/v1/carts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10042" \
-d '{
"mode": "one_time",
"currency": "EUR",
"customer": {
"external_ref": "user-381",
"name": "Dupont Conseil",
"email": "compta@dupont.example",
"siren": "552100554",
"address": { "line1": "3 place Bellecour", "postal_code": "69002", "city": "Lyon", "country": "FR" }
},
"lines": [
{ "label": "Formation Go (1 jour)", "quantity": "1", "unit_amount": "490.00", "tax_category": "S", "tax_rate": "20.00" },
{ "label": "Support de cours", "quantity": "2", "unit_amount": "15.00", "tax_category": "S", "tax_rate": "5.50" }
],
"success_url": "https://shop.acme.example/merci",
"cancel_url": "https://shop.acme.example/panier",
"tags": { "shop": "formations", "order": "10042" }
}'
| Field | Role |
|---|---|
mode | one_time here; see Subscriptions and Pay-as-you-go |
currency | one ISO 4217 currency per cart |
customer | your customer; external_ref is your identifier: the same external_ref finds the same customer |
lines[] | label, quantity, unit_amount, tax_category, tax_rate; optional: description, unit_code (C62 unit, HUR hour…), period_start, period_end, tags |
success_url, cancel_url | return to your site; must be listed in carts.allowedReturnURLs |
ttl_seconds | link lifetime (300 s minimum, carts.maxTTL maximum, carts.defaultTTL by default) |
providers | restrict the providers offered (["stripe"]); by default all those of the account |
tags | free tags (20 at most) to know who the money belongs to, see below |
VAT categories (tax_category): S standard rate, Z zero rate, E exempt, AE
reverse charge, K intra-community supply, G export, O outside the scope of VAT.
What you get
{
"id": "cart_01J9ZQ4K7M2V8X3N5P6R",
"status": "open",
"mode": "one_time",
"currency": "EUR",
"lines": [
{ "label": "Formation Go (1 jour)", "quantity": "1", "unit_amount": "490.00", "tax_category": "S", "tax_rate": "20.00", "amount": "490.00" },
{ "label": "Support de cours", "quantity": "2", "unit_amount": "15.00", "tax_category": "S", "tax_rate": "5.50", "amount": "30.00" }
],
"totals": {
"subtotal": "520.00",
"tax": "99.65",
"total": "619.65",
"taxes": [
{ "category": "S", "rate": "20.00", "basis": "490.00", "amount": "98.00" },
{ "category": "S", "rate": "5.50", "basis": "30.00", "amount": "1.65" }
]
},
"payment_url": "https://pay.dev.stackngo.fr/acme/…",
"expires_at": "2026-10-09T10:15:00Z",
"created_at": "2026-10-08T10:15:00Z",
"tags": { "shop": "formations", "order": "10042" }
}
2. Send the link to your customer
Redirect your customer to payment_url, or send them the link by e-mail. They see your logo,
your colours, the cart details and the payment methods offered. They have no account to
create.
After paying, they are sent back to success_url; if they give up, to cancel_url.
3. Follow the payment
The simplest way is to listen to the payment.succeeded webhook (see Webhooks).
You can also read the cart again:
curl -s https://billing-api.dev.stackngo.fr/v1/carts/cart_01J9ZQ4K7M2V8X3N5P6R \
-H "Authorization: Bearer $TOKEN"
status | Meaning |
|---|---|
open | waiting for payment |
processing | payment being confirmed by the provider |
paid | paid: payment_id is set, the invoice is issued |
expired | link expired without payment |
To withdraw a link sent by mistake: DELETE /v1/carts/{id} (rejected with 409 if the cart
is already paid).
Who the money belongs to
Each payment records the application that requested it (source_app, the client
identifier of the application) and your tags (tags). The payments report groups them:
curl -s "https://billing-api.dev.stackngo.fr/v1/reports/payments?from=2026-10-01&to=2026-10-31&group_by=source_app,tag:shop" \
-H "Authorization: Bearer $TOKEN"
{
"from": "2026-10-01",
"to": "2026-10-31",
"group_by": ["source_app", "tag:shop"],
"rows": [
{ "keys": { "source_app": "6f1c…", "tag:shop": "formations" }, "currency": "EUR", "gross": "12450.00", "refunded": "490.00", "net": "11960.00", "count": 31 },
{ "keys": { "source_app": "6f1c…", "tag:shop": "librairie" }, "currency": "EUR", "gross": "1830.40", "refunded": "0.00", "net": "1830.40", "count": 112 }
]
}
group_by accepts source_app, currency, mode and tag:<key>, comma-separated. For the
detailed list: GET /v1/payments?tag=shop:formations.
What you can expect
- An unpaid cart is not kept: after
expires_at, the link no longer works and the cart disappears. Only the payment creates an order and an invoice. - Replaying the same creation with the same
Idempotency-Keyreturns the same cart, without a duplicate. - Amounts are recomputed by the platform: each line's
amount, then VAT per category and rate. These are the amounts that appear on the invoice. - The money goes to your Stripe or PayPal account, depending on the method your customer chose.
If it does not work
| What you see | Cause | Fix |
|---|---|---|
422 with errors[] | missing or invalid field (non-decimal amount, currency, two-letter country…) | fix the field reported |
403 on creation | one_time not in billingModes | add it to the BillingAccount |
422 on success_url | address not in carts.allowedReturnURLs | add it |
422 on ttl_seconds | above carts.maxTTL | lower it, or raise maxTTL |
| The customer sees "link expired" | cart expired | create a new cart |
See also Payments troubleshooting.