S24 — Price-checker kiosk (a page on the shop floor, priced by the shop)
What the shop does
A tablet or screen stands on the shop floor. A customer scans an item and reads its name, its picture and its price. The price is the shop's own: the store's price list price for one unit, in the scanned unit, found and converted by the shop itself (the same price list lookup the till starts a sale from). Nobody signs in on the tablet; the shop switches each kiosk on and off in Desk.
| On the kiosk | When |
|---|---|
| name, picture, Price and Any offer is worked out at the till. | the scan finds an item with a price on the store's list |
| "We could not find this item. Please ask a member of staff." | nothing matches the scan |
| "This item has no price here. Please ask a member of staff." | no price on the store's list (never shown as free) |
| "This item is not on sale. Please ask a member of staff." | the shop refuses to price it (disabled, past its end of life, a template) |
| "This kiosk is not set up. Please ask a member of staff." | the kiosk's record is missing or switched off |
| "Price checks are not available right now." | the shop or the service cannot be reached |
Offers are not shown. On iVendNext a selling offer is a promotion, and the engine works promotions out on a whole sale at the till (it refuses a selling price rule), so a one-unit price check cannot ask for one; the kiosk says Any offer is worked out at the till. under every price. It never shows more than the store's list price. The recipe: recipes/price_checker.md.
| File | What it is |
|---|---|
manifest.json |
the record type PP PRC Kiosk (one record per screen; its name is a random code; it takes its company, price list, currency, walk-in customer and store from the till profile chosen on it), its help address, and a feed registration that names the app's one integration user. Nothing else: no till key, no check, no field on the shop's own records |
price.py |
the pure core: the kiosk's code and scan, the kiosk's date, the Kiosk record, the arguments for the shop's line pricer (no price rule, never a rate), and the answer |
service/app.py |
the service, standard library only: the page (GET /), POST /prc/price, GET /health, and the app template's handshake for many shops; on CitiXsys hosting it runs as a function (lambda_handler) |
service/kiosk.html · kiosk.js · kiosk.css |
the kiosk's page: a scan box that keeps the focus, the answer drawn as text only, cleared after 20 seconds |
Dockerfile · app.json |
the image for your own server, and the settings file for hosting (a function, paid per call) |
tests/ |
the core, the service over the wire (one shop and many, the function entry, the shop's three reads against a stand-in shop) and the manifest; canaries.py breaks each guarded rule and expects its test to fail |
What the shop does to set it up
- Before install: make the role PP Integration if the shop does not have it. The install makes an app's own roles only when they are named under its prefix (
PP PRC …);PP Integrationis the kit's shared role, so the shop makes it. - Make the app's integration user (the integration user recipe), give it the role PP Integration, generate its API key, and choose it on the feed registration PP PRC Kiosk feed. The service reads with that key and writes nothing.
- Make one PP PRC Kiosk record per screen: where it stands, and the store's till profile. The record's name is the kiosk's code.
- On the tablet, open the app's address with
#shop=and the shop's site, then&kiosk=and the record's name, in full screen. A barcode scanner connected to the tablet as a keyboard (USB or Bluetooth) scans into the page.
Run it
python3 -m unittest discover -s samples/s24_price_checker/tests -p 'test_*.py'
python3 samples/s24_price_checker/tests/canaries.py
python3 samples/s24_price_checker/service/app.py samples/s24_price_checker/service/.env.local
Settings, one shop (service/.env.local, never committed): IVN_SHOP_URL, IVN_SHOP_KEY (api key:api secret of the integration user), PORT. Many shops (the app template's handshake): IVN_LIBRARY_APP and a store (IVN_SHOPS_TABLE on CitiXsys hosting, else IVN_SHOPS_FILE); each shop hands over its integration user's key when it connects, and each kiosk's call names its shop. The shop makes that user after the install, so its key can only travel on a later connect (Connect again); until it does, the kiosk says it is not set up. The site connects only to the one *.ivendnext.app address in the manifest's allowed_urls (the POS release that carries the hosted handshake, train T10); the sample names https://prc.partner.example, so write your own. PRC_PREFIX names the Kiosk record type (default PP PRC).
Technical notes
- How the page finds its shop. A partner page (chapter 9) is opened by the till and its call names no shop. A kiosk is not opened by the till: it is a browser, so its own address names its shop (
#shop=) and its kiosk (&kiosk=). The part after#never reaches a server log. The service answers only for a shop that connected and a Kiosk record of that code that is switched on. - Only for public, read-only screens. The address is the only key, so this pattern fits what anyone in the shop could see (a price). A member's balance, an order or anything that writes needs a sign-in.
- The code is the framework's random name (10 characters of base 32, about 50 bits), made by the shop. Whoever has a kiosk's address can check prices at that store, as anyone standing in it can. Switching the Kiosk off stops its address within a minute: the record is read with the app's key and kept one minute. A run of unknown codes (20 a minute, per copy of the service) stops being looked up in the shop; a code the shop has made is always looked up, so guesses cannot lock a real kiosk out of a copy that has seen it. ⚠ Both the count and the codes seen are kept in each copy's memory. On CitiXsys hosting S24 runs as a function, whose copies start and stop on their own: a new copy has seen no kiosk, so someone who knows a shop's site address and keeps sending made-up codes can make that shop's real kiosks say Please wait a moment while it lasts. The same count tells them, by its Please wait, that the site uses the app. Keep both in your shops store if that matters to your customers. Whoever holds a real kiosk's address can check as often as they like, and each check is two calls to the shop.
- The price is the shop's own. For each scan the service makes two calls with the app's key, both whitelisted calls of the shop:
ivendnext_mpos.api.v1.scan_barcode(a barcode, a serial number or a batch; the same lookup the till's scan composes) andivendnext_mpos.api.v1.get_item_details, the shop's line pricer, for one unit, in the scanned unit (a price in the stock unit is converted), on the store's price list, for its walk-in customer (a price row for that customer first), on the kiosk's own date (within a day of the service's), with no price rule. ⛔ The service never sends arate: given one, the pricer may write a price row when the shop's auto-insert price setting is on. - What it sends outside: the shop's site sends only the hosted handshake: its App library key, its address and the integration user's key, at install, update, Finish and Connect again. The service reads the Kiosk record and the shop's two answers for each scan; the pricer's answer carries the item's details (its name, unit, picture, price, description, group, brand and accounts) and the store's stock figures, of which the kiosk shows the name, unit, picture and price and the service keeps none. The kiosk sends the service the scanned text, the kiosk's code, the shop's address and the tablet's date. It logs one line per call: the route, the status and the milliseconds, never the scan or the code.
- The page is served with a content policy that allows only the app's own script and style, and pictures from any https address; it draws every answer as text. The service passes a picture only when it is one of the shop's public files (
/files/…), and builds its address from the shop's own. - Bounds: offers are not shown (above); a walk-in customer whose customer group has its own price list is priced on the store's list; a typed item code is not looked up (1.0); the price is shown in the till profile's currency, converted by the shop's own exchange rate when its price list is in another (read in the engine's pricer, not run on our bench); the kiosk is online only.
What was proven where
- Laptop: the tests above (52) and 23 canaries, each RED with its baseline passing. The page in a real browser (the laptop, 2026-10-15), against a stand-in shop: a scanned barcode followed by Enter showed the item, its unit and ZAR 12.50 with the offers line; an item with no price and an unknown barcode showed their sentences; an address with no kiosk said not set up; the scan box kept the focus throughout.
- Test bench (2026-10-15, the iVendNext POS app at
e149dce4; runpp-20261015T045526Z-22519, on the reviewed code), 20 of 20 checks, the service in one-shop mode reading the bench shop with the integration user's key: the install made the Kiosk record type with random names and its help address, and the feed registration, with no service and no secret; a Kiosk took its company, price list, currency, walk-in customer and store from the till profile; the page was served with its content policy; two items scanned by barcode showed 12.50 and 7.25, the same as the till's own price for one of each (api.mpos_cart.recompute, in a day opened and closed); an item with no price said no price here; a disabled item said not on sale; an unknown barcode and a typed item code said could not find; a kiosk switched off, and a code the shop never made, said not set up (the shop's real 404 names the record); price rows, price rules, items and kiosks counted the same before and after the scans (the bench's auto-insert price setting is off, so this cannot see the one write the pricer can make; that the service never sends arateis proven by the laptop tests); switched off in Desk, the kiosk said not set up after the service's minute; the service's log carried no scan and no code; uninstall left the schema as found; no POS day left open. Not run on the bench: many shops (the laptop tests; the product sends the handshake since train T10), the shop down (the laptop tests), a promotion (not shown, by design).
Needs this serves
- A price checker shoppers use on the shop floor — Ready, with limits (1)