E-commerce · Payments
Kubi’or
Selling a Beninese bouillon online when the card rail does not speak your currency.

The problem
Kubi’or sells a natural bouillon in three pack sizes. The brand had no way to take a card payment: Stripe Checkout does not settle in XOF, the currency its customers price in. The naive fix — record the order in the browser after the redirect — loses every sale where the buyer closes the tab, and duplicates every sale where Stripe retries.
The solution
Prices are displayed in FCFA and settle in USD through Stripe Checkout, with shipping restricted to Benin. The order record is never written by the browser. It is created by a signature-verified webhook, and a unique constraint on the Stripe session id makes that write idempotent, so a retried event can only ever produce one order.
Key features
- Three packs — 5, 15 or 30 sachets — each mapped to its own Stripe price ID through environment variables, so pricing changes without a deploy
- Hosted Stripe Checkout: no card data ever touches the application
- Billing address required, shipping limited to Benin
- Success page that shows the real order, read back from the database rather than from the redirect URL
- Cancel page for abandoned checkouts, with no order written
Architecture
Checkout creation
The product card posts the chosen pack to a serverless function. The function maps the pack to a Stripe price ID, creates the Checkout session with the pack carried in metadata, and returns the hosted URL. An unknown pack is rejected before Stripe is ever called.
Payment
The browser is redirected to Stripe. Card details, authentication and receipts are Stripe’s responsibility, not the application’s.
Webhook
Stripe posts the completed session to a second function with the platform body parser disabled, because signature verification needs the untouched raw bytes. On a verified checkout.session.completed the order is inserted with a service-role key. Every other event type is acknowledged and ignored.
Confirmation
The buyer lands on the success page, which polls a read-only order endpoint until the row exists. The redirect and the webhook race each other; the poll is what makes that race invisible.
Tech stack
Technical challenges
Stripe Checkout has no XOF, but the customers price in FCFA and the brand cannot quote in a foreign currency.
Display prices in FCFA and settle the charge in USD. The pack, not the sachet, is the priced unit, so the conversion is a fixed decision per pack rather than a live rate the customer has to second-guess.
Stripe retries a webhook until it gets a success response. A retry after a successful insert would create a second order for a single payment.
A unique constraint on the Stripe session id. The insert is attempted unconditionally; a unique-violation from Postgres is caught and answered with a success response, which both stops the retries and guarantees exactly one row per session. The database enforces the invariant, not application code.
The buyer is redirected back before Stripe has finished calling the webhook, so the order often does not exist yet when the success page loads.
The success page polls the order endpoint a bounded number of times at short intervals instead of reading the order out of the URL. Nothing about the order is ever trusted from the client.
Order rows must never be readable or writable from the browser.
Row-level security is on with no anonymous policy at all. Reads and writes both go through server functions holding the service-role key, which never reaches the client bundle.
Results
- One order per payment, guaranteed at the database level rather than hoped for in application code
- Card payments accepted in a market whose currency the processor does not support
- No order state derived from the client: the only thing that creates an order is a signed webhook
- A closed tab or a failed redirect no longer costs a sale