Skip to main content

Collect a payment (cart)

Beta

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), with one_time in billingModes.
  • A token of the application declared in your account, with the carts scope.

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" }
}'
FieldRole
modeone_time here; see Subscriptions and Pay-as-you-go
currencyone ISO 4217 currency per cart
customeryour 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_urlreturn to your site; must be listed in carts.allowedReturnURLs
ttl_secondslink lifetime (300 s minimum, carts.maxTTL maximum, carts.defaultTTL by default)
providersrestrict the providers offered (["stripe"]); by default all those of the account
tagsfree 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"
statusMeaning
openwaiting for payment
processingpayment being confirmed by the provider
paidpaid: payment_id is set, the invoice is issued
expiredlink 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-Key returns 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 seeCauseFix
422 with errors[]missing or invalid field (non-decimal amount, currency, two-letter country…)fix the field reported
403 on creationone_time not in billingModesadd it to the BillingAccount
422 on success_urladdress not in carts.allowedReturnURLsadd it
422 on ttl_secondsabove carts.maxTTLlower it, or raise maxTTL
The customer sees "link expired"cart expiredcreate a new cart

See also Payments troubleshooting.

See also​