Kartier
Taking orders online without handing your margin — or your customers — to a marketplace.
A small food business that wants to sell online can either join a marketplace, which takes a percentage of every sale and puts its own brand in front of the customer, or do nothing. Kartier is the third option: your own storefront, on a flat subscription, with no commission.
- Category
- Multi-tenant SaaS platform
- Platforms
- Web · PWA
Where it started
The product serves snacks and restaurants in La Réunion, click-and-collect: the customer orders and pays online, then collects on site. No delivery. This is a product I designed and operate rather than a client commission — which changes how it is built, since every decision is paid for later in support time or in a cancellation three months in.
Two separate problems meet at the same counter. For the merchant, a marketplace commission eats into an already thin margin, and the end customer belongs to the platform rather than to the restaurant. At the counter, the rush is the real bottleneck: spoken orders get written down wrong, queues build, and a paid order nobody sees costs a customer and a reputation.
What was built
Each establishment gets its own storefront, generated from a single brand colour: no per-client design work, no separate deployment. Customers order without creating an account, pick a collection slot and pay online. The merchant works from a tablet at the counter: an order board readable from two metres, an alarm that repeats until someone acknowledges it, and one-tap status changes. They manage their own menu, prices, VAT and hours — and have an emergency stop for when the kitchen is swamped.
What the product does
A storefront in your name
Customers order from an address that belongs to you, in your colours. You build your own customer base, not a marketplace’s.
No commission on sales
A flat subscription, with nothing that scales with your volume. Your margin does not shrink as you sell more.
An order board built for the rush
Three columns readable from two metres and an alarm that repeats until acknowledged: a paid order is never missed during service.
Capped collection slots
Customers book a fifteen-minute slot, within a limit you set. Orders arrive spread out instead of all landing at noon.
Your menu, prices and stock
Per-product VAT, option groups, photos, and a “sold out today” that resets itself the next morning — without going through me.
Emergency stop and delay notices
A switch to stop taking orders, and delay buttons that push back the collection time and tell the customer. The kitchen stays honest even when swamped.
Monthly exports ready for the accountant
A server-generated CSV and PDF with VAT split by rate and the cash-versus-online breakdown. Month end becomes a download.
Loyalty stamps and promo codes
Stamps earned at collection and discount codes limited per customer, without adding a second tool to manage.
The decisions that mattered
Write the order before charging the card
The order, the server-side recalculation of the total and the slot reservation are written in a single transaction; only then is the payment requested. The order of those two steps is not incidental: a failed write has therefore never charged anyone, and “customer charged, order does not exist” becomes structurally impossible. The reverse case — charged, order stuck — is caught by an automatic check that queries Stripe every ten minutes. The customer-facing consequence: someone who has been charged always sees “your payment went through”, never a failure message.
Three nets against processing a payment twice
Stripe guarantees delivering its notifications at least once, without guaranteeing order or uniqueness. Processing therefore dedupes on the event id, but also on the object-plus-event-type pair — because two distinct events can describe the same fact. Finally each transition is written to be replayable with no effect: applying “paid” to an already-paid order does nothing. Three independent protections, because a payment counted twice is a customer dispute.
Finding your order from a different phone
The tracking link sent by SMS opens in the phone’s default browser — almost never the one the order was placed in. Without a dedicated mechanism the customer loses access to their own order. The solution stores only the token’s fingerprint, never the token itself: a database leak grants nothing. And the authorisation is deliberately scoped to the establishment-plus-order pair, because two establishments can hold orders with identical ids.
Forbidding backward steps on an order’s status
The access rules constrained which fields could change, never to what values. As a result a refunded order could go back into preparation, and a staff member whose screen lagged by two seconds could push an order from “ready” back to “preparing” — after the SMS had gone out. Counter-writable statuses are now limited to four, and refunds run through a dedicated function: letting a tablet mark “refunded” would record a refund nobody paid.
A timezone that is not the customer’s
La Réunion is UTC+4 and does not change clocks, while the customer’s phone may be set to Paris, which changes twice a year. A miscalculated service day files an order under the wrong day, or the wrong accounting month. The subject has its own test series, including at daylight-saving boundaries, plus a real-browser check whose instruction is explicitly to set the phone to Paris — not to local time.
A guard against test suites that shrink in silence
On a fresh machine, a missing folder quietly dropped eight test files from collection. The report read “192 passed”, in green — and nobody compares 192 to 260 from memory when the colour is right. Each job now publishes its test count, and a final step compares them against a floor. Floors rather than exact counts, deliberately: an exact count needs updating on every added test, and that ritual ends up being rushed.
How it fits together
Application client
One storefront per establishment, in its own colours, nothing to install
Console de service
The counter screen: live orders, readable from two metres
Fonctions serveur
Everything touching money and status: recalculation, payment, transitions
Cloud Firestore
Data partitioned per establishment, with the rules as the only boundary
The technologies used
Frontend
- Flutter Web
- Dart
- BLoC
- go_router
- PWA
Backend
- Cloud Functions
- TypeScript
- Node.js
Infrastructure
- Firebase
- Cloud Firestore
- Firebase Hosting
- GitLab CI
Services
- Stripe Connect
- OVH SMS
What was delivered
Platform designed and built solo: three web applications, twenty-six server functions, roughly 87,000 lines of Dart and 37,000 of TypeScript, and close to 2,800 automated tests — over 500 of them covering nothing but the access rules between establishments. Continuous integration refuses to build anything if those rules fail. The platform is deployed and publicly reachable, in its shakedown phase: payments run against a test environment and no real transaction has taken place yet. Operating results will be added once they exist.
- Web
- PWA
Need a similar product?
Let’s talk about what you want to build. I reply personally, usually within 24 hours.