L'art Napulè, "La Casa del Napoli", is a Neapolitan pizzeria on Boulevard de Clichy, in Pigalle, Paris 18e. Its site, artnapule.fr, carries "Propulsé par TuniCyberLabs" in the footer. This is how that platform is put together, and why.
The shape of the platform
It is an ordering platform with three channels, a loyalty engine that requires no account, and loyalty cards that live inside Apple Wallet and Google Wallet. The menu is the least interesting part. What happens after checkout is the actual product.
- ▸Delivery, with the postcode verified in the cart before payment, limited to 75018, 75009 and 75017.
- ▸Click and Collect: the customer picks a pickup slot, is notified when the order is ready (typically 20 to 30 minutes), and the order is held 30 minutes after the slot.
- ▸Dine-in by scanning a QR code on the table, which opens the menu with the table number already filled in.
- ▸Loyalty across all three, keyed on the phone number or email used to order.
- ▸Bilingual French and English, installable as a PWA, card payments through Stripe.
Why wallet passes beat a loyalty app for one restaurant
A single restaurant cannot win the app install. Download, register, verify, then keep an app that is useful twice a month: that funnel loses people at every step. A wallet pass skips the whole thing. It is added in one tap and then lives next to boarding passes and event tickets.
The economics are just as lopsided. A one-venue app means two native builds, two store review queues, push certificates and a permanent update treadmill, and the reward is an icon buried on page four of a home screen. A wallet pass has no binary, no store listing and no version to maintain: it is a signed data object the operating system already knows how to render, store and refresh. At the till the customer shows a card from the wallet they already open for transport: no login screen, no password recovery.
How a self-updating loyalty pass works
Conceptually: a pass is issued per customer, the server holds its current state, and when the point balance changes the server signals the wallet, which fetches the updated pass and redraws it. The customer does nothing. There is no app to update because there is no app.
The general pattern, how such systems are built rather than a claim about undocumented internals, has four parts:
- ▸A pass template describing layout, fields and branding, signed with a certificate issued by the wallet platform.
- ▸A per-customer instance of that template carrying a unique serial, the balance, and a link back to the issuing service.
- ▸A registration handshake: when the pass is added, the wallet registers the device against that serial.
- ▸A change signal: when the balance moves, the service notifies the registered devices and each wallet pulls the fresh pass.
On artnapule.fr the balance updates by itself after each order, and the card is presented like a boarding pass without opening the site. Five designs exist, one assigned per customer, revealed when they look up their balance by phone or email and tap Apple Wallet or Google Wallet. Push notifications are opt-in and used for two events only: the order is ready, and a reward has unlocked.
Loyalty without accounts: the phone number is the key
Points are credited automatically after every paid order, matched on the phone number or email used to order. No account, no cardboard card, no password to reset. When the threshold is reached the reward arrives as a promo code by email or SMS. The identifier the customer already typed to receive their food becomes the loyalty key.
That is the right trade for a restaurant, but it is a trade, and the costs deserve naming:
- ▸Identity is soft. Someone who orders once with a mobile and once with a work email holds two balances. A lookup that accepts either field, plus merge tooling, mitigates it.
- ▸The key is guessable. Anyone who knows a phone number can query a balance, so the lookup must expose as little as possible and rewards must go out of band, to the contact on file, never on screen.
- ▸Consent and retention need care. A phone number tied to purchase history is personal data under GDPR: lawful basis, retention period, deletion path. We went through that reasoning in GDPR Compliance for SaaS Startups: A Practical 2026 Guide.
Three ordering channels, one order model
Delivery, collection and dine-in look like three products to a customer and should be one order model in the code: same cart, same menu, same loyalty credit, same promo engine. What differs is a fulfilment mode and the constraints that mode imposes on validation.
Modelling it any other way is how restaurant platforms rot: three cart implementations means three places to apply a discount, three to compute allergens, three where the loyalty credit can silently fail. Keeping fulfilment as an attribute of the order, rather than a fork in the codebase, is what lets you say "points are credited after every paid order" without a footnote per channel. The constraints then hang off the mode: delivery needs a serviceable postcode, collection needs a slot and a hold window, dine-in needs a table number.
Why postcode verification belongs in the cart, not after payment
Because a customer who has paid for a delivery you cannot make is now a refund, a phone call and a bad review. Verifying the postcode while the basket is still editable turns an operational failure into a two second correction. artnapule.fr checks it against 75018, 75009 and 75017 before payment.
The ordering of validations is a design decision, not an implementation detail. Every check that can invalidate an order belongs before the payment intent is created: serviceable area, kitchen open, slot still available, items still in stock. Anything discovered after authorisation costs money and trust to unwind.
QR code ordering at the table
The customer scans the code on the table, the menu opens with the table number pre-filled, they order, and on validation the order goes to the kitchen. Payment is online or at the till depending on the service setting. No app, no waiter round trip for the first order, no reprinting paper menus when a price moves.
The pre-filled table number is the whole trick. It carries the context that makes an order routable, which is exactly the piece a customer should never have to type and would frequently get wrong. Encode the context into the QR target, then confirm it visually in the interface.
The wheel: every spin wins, and no review is ever asked for
Scan a QR code in the window or on the table, leave a first name and an email or phone, spin. Every spin wins: there is no losing slot. The prize arrives as a single use promo code by email or SMS and applies automatically at checkout.
Observed prizes include 10% off an order, a free drink, 10% off a next order, a free sauce, a free pizza and a free menu.
Two decisions here deserve stating plainly.
- ▸No losing slot. A wheel that can lose turns a fun moment into a small resentment. Every spin winning keeps the acquisition cost bounded (the prize mix is the lever) and the interaction net positive.
- ▸No review requested in exchange. Conditioning a reward on an online review is review gating. It breaches the policies of the major review platforms and it poisons data everyone else relies on. Not asking is both the compliant answer and the better one: the prize buys a second visit, which is the thing that actually pays.
Single use enforcement is the part to get right server side. A promo code that can be replayed is a discount with no floor.
Payments: Stripe, SCA, and where a discount may apply
Card payments run through Stripe, with strong customer authentication when the bank requires it, and no card data kept on the platform's own servers. Promo code discounts always apply to the product total, never to delivery fees. Both of those are policy decisions enforced in code, not incidental behaviour.
- ▸SCA is not optional in Europe. Under PSD2 the flow has to hand control to the bank for a challenge and resume cleanly. The failure mode to design against is an order that exists but is unpaid because the challenge was abandoned.
- ▸No card data on the servers collapses PCI DSS scope: the payment provider captures the details, the platform holds a reference.
- ▸Discounts on product total only protects unit economics. Delivery is a cost genuinely paid out, so a percentage that eats it turns a promotion into a loss on every basket.
PWA instead of a native app
The site can be added to the phone home screen and opens full screen, keeping the loyalty card at hand. Nothing to download, nothing to update, no store review between a fix and the customer. Both routes are documented on the site: iOS through Safari, Android through Chrome.
For a restaurant, a native app buys almost nothing a PWA does not provide and costs two build pipelines plus two review queues. The one capability that genuinely matters, a card presentable at the counter without opening anything, is handled by the wallet pass. The wallet does the wallet job, the PWA does the ordering job.
Consent, allergens, and the unglamorous half
Google Analytics fires only after consent, no cookie is dropped before the visitor accepts, and there is no targeted advertising. The menu lists allergens per product, following EU regulation 1169/2011. This is the half of a restaurant platform that a generic template usually gets wrong.
Allergen data has to be structural, attached to the product record and rendered everywhere the product appears, rather than a paragraph someone maintains by hand. Gating the analytics tag properly is likewise a few lines of logic, and skipping it is a fine waiting to happen.
Want something like this built
This is a class of system we build routinely: ordering plus loyalty, with real payment, real compliance, and no app install in the way. We applied the same thinking in TuniReach: The All-in-One Marketing CRM We Built (and Why We Built It), and to a free public platform in TuniRise: The Free Platform We Built for Tunisian Youth.
If you run a restaurant, a chain, or any business where the loyalty card should sit in the customer's wallet rather than in a forgotten app, tell us what you sell and how, and we will tell you what it takes to build. Start at /contact.
