iVendNextDevelopers Request a sandbox

S1 — warranty check (pp_wrc), connected leg

A covered phone sold at the till, and the warranty service's comment on the sale

A phone is sold at the till with its serial number scanned; seconds later the warranty service's comment is on the sale in Desk.

This sample is a small Python service that registers a warranty for every covered item a shop sells, and tells staff on the sale. A sale that is a return, or a consolidated roll-up of till sales, registers nothing, and the sale webhooks themselves were not exercised by its tests. It runs on your own server, outside the shop: a developer new to iVendNext can copy it to react to any booked sale without putting code inside the shop.

When to use it: you want to do something with every booked sale (register it, notify someone, audit it) and show the result to staff on the sale itself.

How it works

When a sale is booked in iVendNext, this small Python service, running on the partner's own server:

  1. receives the signed webhook and checks it (recipes/webhook_receiver.md);
  2. reads the sale's lines and their serial numbers over the record API, as an integration user;
  3. registers a warranty per covered serial number in its own store (SQLite), using its own terms;
  4. writes one Comment on the sale, which staff see on the sale in iVendNext Desk.

A reconciliation pull finds every sale whose webhook never arrived. Nothing is added to the shop's database except that Comment.

What it looks like

The phone is the sample's own test item (PP-WRC-PHONE, covered for 24 months by service/terms.json). The comment is signed by the shop's integration user, PP Integration.

Step Frame Screenshot
1. A sale on the till: the covered phone and a leather case, for a named customer the till, 1440 wide
2. The till asks for the phone's serial number before it charges the till
3. The sale is booked (here with a demo card) the till
4. The warranty service's comment on the sale: one warranty, until 2028-10-07; the case not covered Desk

The files

File What
core.py The pure core. warranty_answer(context, terms) gives the till's answer, registrations(...) the warranties of a booked sale, and comment_text(...) the Comment. It imports no framework. context is shaped exactly like the till's app-button context, and the answer uses only the till's closed vocabulary, so the same file can be bound at the till unchanged
service/app.py the HTTP service (standard library): POST /webhook, POST /reconcile and GET /status (both need X-PP-Admin), GET /health
service/receiver.py signature check and the replay key
service/tenant.py the only door into iVendNext: the record API calls of api/collection/
service/store.py the partner's own store: terms, warranties, handled sales, seen-set, watermark, events
service/terms.json the partner's warranty terms (item or item group → months)
tests/test_core.py tests of the core on recorded sale contexts (tests/contexts/)

Two ways to use the same core

Outsiders never write Python inside a shop's site. Both forms below run on your own server, and both are public: an outside partner may build either.

Form What Who may use it
Connected — service/ the partner's own service: reads booked sales (a webhook nudges it today; the reliable route is now the event feed, recipes/event_feed.md), keeps warranties in its own store, comments on the sale public — the form an outside partner builds
Remote till key — remote/wrapper.py the same pure core answering the till's "Warranty" key from the partner's own server (the remote key contract) public

Run it

python3 -m unittest discover -s samples/s1_warranty/tests -p 'test_core.py' -v
python3 samples/s1_warranty/service/app.py samples/s1_warranty/service/.env.local

The service reads one env file (never committed; chmod 600), kept at samples/s1_warranty/service/.env.local: PP_BASE (the shop's address), PP_KEY and PP_SECRET (the integration user's API key), PP_WEBHOOK_SECRET, PP_ADMIN_TOKEN (for /reconcile and /status), and optionally PP_PORT (8121), PP_DB, PP_TERMS, PP_SINCE and PP_RECONCILE_SECONDS. Run it on a server that stays up, so no delivery is dropped; the shop's webhook points at its /webhook.

What is refused, and why

The service refuses a delivery whose signature does not check, and a delivery it has already seen (a replay). That way nobody but the shop can make it register a warranty, and a sale delivered twice is handled once.

A sale that is a return, or a consolidated roll-up of till sales, registers nothing.

When your service is down

A sale booked while your service is down is not lost. The reconciliation pull (POST /reconcile) finds every sale whose webhook never arrived. Run it nightly and after every restart.

Technical notes

The walk

The GIF: a cashier sells a covered phone and a leather case to a customer, scans the phone's serial number, and takes the card; seconds later the warranty service's comment is on the sale in Desk: one warranty registered, the case not covered. Frames: the till (/pos?frame=terminal, 1440 wide), then Desk. Walked on our test bench on 2026-10-07, iVendNext POS app at 8dc36aab8. Built from the same screens as the stills below, uncropped.

The stills: walked in a real browser on our test bench, 2026-10-07, iVendNext POS app at 8dc36aab8. The cashier is the bench's Thabo Nkosi, the customer Naledi Khumalo; the phone is the sample's own test item (PP-WRC-PHONE, covered for 24 months by service/terms.json). The comment is signed by the shop's integration user, PP Integration. Step 3's card is the bench's demo card.

The tests

File What
tests/test_core.py core tests on contexts recorded from the bench's own build_context (tests/contexts/)
our test bench's own run (not in the kit) the end-to-end tests: functional, signature, replay, idempotency, three failed deliveries → reconciliation
our test bench's own run (not in the kit) a canary that proves the end-to-end tests can fail: a receiver with no signature check and no replay set must turn them red

On our test bench the service ran beside the shop and passed its end-to-end test (signed delivery, replay refused, reconciliation).

The two forms

The forms table dates from 2026-10-04 — the rule: outsiders never write Python inside a tenant. The remote till key was, on our test bench, pressed through the released key over HTTPS in row BUMP's run of 2026-10-07 (G5b, run pp-20261007T014940Z-64695): through the till's own press endpoint, called from the bench's console as the cashier, not from the till's screen. A press of Warranty from the till's screen or from a physical till device was not tried.

The env file's path

service/app.py opens the path it is given from the current directory (load_env, open(path)), so from the kit's root the command passes samples/s1_warranty/service/.env.local. The remote binding reads the same file at that place (remote/wrapper.py: os.path.join(SAMPLE, "service", ".env.local")), so keep it there. Our test bench runs the same command from the sample's own folder as python3 service/app.py service/.env.local.

Why it is built this way (R6)

Option Cost Verdict
Standard library only (http.server, urllib, sqlite3) ~300 lines; nothing to install on the partner's server or the mini picked: any Python developer reads it unaided
Flask or FastAPI shorter handlers; a virtual environment and dependencies on the mini rejected for a reference sample
A generated client from api/openapi.yaml adds a build step; the calls are six plain HTTP requests not needed (G3 makes it optional)

Bounds, stated

The reconciliation timer is off on the bench (PP_RECONCILE_SECONDS=0) so the tests drive it. A partner runs it nightly and after every restart. A sale that is a return, or a consolidated roll-up of till sales, registers nothing. POS Invoice webhooks are subscribed but not exercised by these tests, which open no POS day.

This page in the kit