Checkout
A hosted checkout page that quotes the tax from where the customer is, takes the payment, and records the sale only when the processor confirms it, with the invoice, the ledger and the thresholds in one step.
Checkout sells your one-time prices and credit packs on a page Ownrecord hosts. You create a checkout session with the API, send the customer to its URL, and get the result on your success page and in webhooks. Sessions in subscription mode start subscriptions, with trials and promotion codes, sessions in topup mode add credits to an existing customer's wallet, and sessions in setup mode save a payment method.
Creating a session
POST /api/v1/checkout/sessions
Idempotency-Key: 6f1c…
{
"lineItems": [{ "lookupKey": "credits_100", "quantity": 1 }],
"customerEmail": "anna@example.com",
"successUrl": "https://shop.example/thanks",
"cancelUrl": "https://shop.example/cart",
"metadata": { "order": "ord_1042" }
}
- Line items: 1 to 20 prices of one currency, by ID or lookup key, with a quantity from 1 to 1,000. For a price with a custom amount (for example "any amount from €5"), give
customAmountin integer micros; it's sold one at a time. Archived prices are refused, and so are recurring prices outsidesubscriptionmode. - Customer: an existing customer, or an email to pre-fill. Buyers become customers when their details are quoted.
- Top-ups:
mode: "topup"sells exactly one credit pack, a fixed pack or a custom amount, to thecustomeryou name (required). The page asks for that customer's email, and a customer who has paid you before is taxed as an existing customer. - Business details:
collectBusinessDetailsisauto(the page asks "Buying for a business?"),requiredornever. - Expiry: a session lives 24 hours by default;
expiresAtcan shorten it to as little as 30 minutes.checkout.sessions.expireends it at once.
The answer is the session with its url: https://ownrecord.io/c/{brand}/{session}?key=…. The key proves the link came from you, because session IDs alone are guessable. Give the URL only to the customer. Once a session uses a price, that price can't change any more.
What the customer sees
The page is mobile-first, shows your brand, and in test mode a banner that says no real money moves. The footer is the merchant disclosure: Sold by your legal entity, its address and its VAT ID.
- Details. Email and country, pre-filled from the customer's IP address, and for a business the company name, VAT ID and address. The VAT ID is checked with its register (VIES, HMRC or the Swiss UID register).
- Quote. The tax engine works out the tax from the location evidence: the billing country and the IP country, and later the card's country. The page shows each line, the tax as "VAT 19% (Germany)" (or "Includes VAT …" for prices that include tax, or "Reverse charge" for a business with a valid VAT ID) and the total.
- Payment. Ownrecord prepares the payment at your entity's processor for exactly the quoted total. With Stripe, the customer pays in the Payment Element; in development and tests, the fake processor offers test cards. When the card is entered, its country joins the evidence and the sale is quoted again. If the total changes, the customer sees the new total and confirms it before anything is charged. With Mollie, the customer accepts the terms here and pays on Mollie's payment page, with a method your Mollie profile offers (cards, iDEAL, Bancontact, …), then comes back to the success page; the sale is invoiced at the quote they paid for.
- Legal consents. The customer accepts your terms. Consumers in the EU and the UK buying digital services that start at once also agree to immediate performance and the loss of their withdrawal right; Ownrecord stores that consent with its time, text version and the customer's IP country.
- Confirm. 3-D Secure runs when the bank asks for it.
- Success. The page waits until the payment is recorded, then sends the customer to your success URL with
?cs_id={session}.
A quote is valid for 30 minutes and is computed again when it expires or the date changes.
When a sale can't go ahead
If Ownrecord can't tax the sale with confidence, it doesn't take the payment: you sell only where your tax position is clear. The session becomes blocked, you get checkout.session.blocked, and the page explains why:
- Not registered yet (for example the UK, which requires registration before the first consumer sale): "Genno can't sell to consumers in the United Kingdom yet". The customer can join the waitlist: "Notify me when Genno can sell in the United Kingdom". The entry is confirmed by a link in an email (double opt-in, valid for 7 days). A business with a valid VAT ID may still buy, reverse charged, where the rules allow it.
- Unclear location: the evidence doesn't confirm one country. The customer can check their country and try again.
Nothing about a blocked customer is stored except the session and, if they ask, their waitlist entry. Ownrecord counts each paused checkout (per country, regime and reason, once per session) without anything about the customer. GET /api/v1/waitlist_entries lists your waitlist; the entity's Demand page (registrations) shows where it pays to register.
When you can sell there
As soon as you can sell to consumers in a waitlisted customer's country, Ownrecord emails them once: "Genno can now sell in the United Kingdom". That happens when a registration change lets you sell there (you arm the registration, or mark it registered) and, daily, when the tax knowledge changes. Before emailing, Ownrecord quotes what the customer wanted to buy, for a consumer in their country, the way a checkout would; only a sale that would go through is announced.
- One email per opening. An entry is marked
notifiedAtand never emailed again for that opening, even if the registration changes again later. A customer who joins the waitlist again after that starts a new double opt-in and hears about the next opening. - A fresh checkout. The email's Continue to checkout link makes a new checkout session at the click, with the lines, URLs and settings of the session the customer joined from, so it works long after a session's 24 hours. The session's metadata has
waitlist_entry. - Leaving. Both waitlist emails have a Leave the waitlist link (
unsubscribedAt): a customer who leaves is never emailed. - Rate. At most 50 emails a minute per legal entity and mode; the rest follow a minute later.
The hosted pages for these links call public operations you can call too: checkout.hosted.resume_waitlist and checkout.hosted.leave_waitlist, each with the token from the email's link.
Fulfilment: only on the processor's word
A purchase is recorded only when the processor's webhook says the payment succeeded, never because the browser reached the success page. Then, in one database transaction, Ownrecord:
- records the payment;
- posts the sale to the ledger (revenue per product and tax per jurisdiction and regime), or funds the customer's wallet for a credit pack or a top-up;
- applies the tax point: a registration change the sale triggers (for example the start of a grace period when it crosses a threshold) and the threshold counters of your legal entity, shared by all its brands;
- locks the customer's tax country at their first payment (a later top-up taxed in another country moves it);
- issues the invoice or receipt with the next number of your brand's series;
- marks the session
completeand queues the events.
The invoice shows exactly the quote the customer paid, even if that quote has expired or your registration changed since. If the amount paid isn't that quote's total (it never should be), nothing is recorded and a RECON_UNMATCHED alert opens for review.
Statuses and events
| Status | Meaning |
|---|---|
open | Waiting for the customer |
blocked | The sale is paused for the customer's location; the waitlist is open |
processing | The customer confirmed; waiting for the processor |
complete | Paid and fulfilled: paymentId and invoiceId are set |
expired | The session's time ran out, or you expired it |
Webhooks deliver checkout.session.completed, checkout.session.blocked, checkout.session.expired, payment.succeeded, payment.failed, invoice.issued and customer.created or customer.updated.
Building your own page
The hosted page uses public operations you can call too: checkout.hosted.get, submit_details, start_payment, attach_payment_method, confirm and the waitlist steps. They take the session key from the URL, or an API key with checkout:write, so you can build your own checkout on the same tax quotes and fulfilment.