iVendNextDevelopers Request a sandbox

S50 — Required sale details (pp_rsd): the details a customer group gives before paying

The retailer decides which sale details a group of its customers must give before they pay: an account customer's purchase order number, an insured patient's policy number, a contractor's project. When the cashier presses Pay and a detail the customer's group needs is empty, the till refuses the sale in the retailer's words. The cashier fills the detail under the sale's details and presses Pay again.

When to use it: a sale detail matters for some customers only. A Sale Attribute's own Required switch holds for every customer on every till; this app holds it for the groups and tills the retailer names.

What happens

When What the cashier sees
A sale for a customer whose group needs a detail left empty "This sale can't be paid" and the retailer's words, or the app's own: "Customers in Account customers need Purchase order number. Fill in the sale's details, then Pay." The cashier fills it and presses Pay again
Every detail the group needs filled The payment screen opens as always
No rule for the customer's group or any group above it, or a till the rule does not name The payment screen opens as always
A refund, the returned items of an exchange, a return rung without its sale Not judged: the refund choices open as always
An exchange's new items Judged as a sale; but an exchange rung without its sale when nothing is owed comes as a return, and is not judged
An exchange, or a sale made from a quote, whose customer's group needs a detail ⚠ The till refuses Pay, but neither screen has a place to fill a sale detail (the exchange screen opens the sale-details sheet only for iVendNext's own required details; a sale from a quote hides it). Fill the detail on the quote before converting it; for an exchange, refund and ring a new sale. Asked of the POS app
The service down, or the retailer's records unreadable The check's down answer: the sale goes on and is flagged on Sales to review
A sale rung offline The till does not call the service: flagged when it reaches iVendNext, never refused
What the retailer and the cashier see (on a demo site, 2026-10-24)
What the install will change, read before you consent
A rule for a customer group: one sale detail needed, at one POS Profile, with the words the cashier reads
The check switched on for one POS Profile, with what it is sent
At Pay, the detail empty: the sale refused in the rule's words
The cashier fills the detail under the sale's details, then presses Pay again

Which rule decides. One rule per customer group: the sale details needed, and the POS Profiles it holds on (none: every till). A customer takes their own group's rule; a group with no rule takes the rule of the nearest group above it in the retailer's customer group tree. A group's own rule replaces the rule above it entirely, so a rule with no details lets a group off:

Customer group Sale details needed Only at these POS Profiles
Account customers Purchase order number (every till)
Contractors (under Account customers) Purchase order number, Project Trade Counter
Staff (none)

A contractor buying at the trade counter gives both; at any other till, nothing (their own rule replaces the account customers' rule). The store's walk-in customer is in a group too: a rule on its group asks every walk-in sale (for a mobile number, say); a rule on All Customer Groups asks the walk-in customer and every customer below it with no rule of its own.

What the retailer does

Before you install: every host.docker.internal address in manifest.json (in allowed_urls, and where the manifest calls it) is our test bench's own address for the sample's service, and it resolves only where Docker provides it. Replace it with your own service's address first, or iVendNext's calls never reach your service; if this manifest is already installed, raise its version and install it again (the manifest recipe, step 6).

  1. Install the app from the App library (or paste its manifest into Retail Extension Package in Desk), read what it will do and consent. The install adds the record type PP RSD Rule with its two tables, the role PP RSD Integration, the check and the feed registration, the check switched off. Nothing changes at the till yet.
  2. Make the app's integration user (the integration user recipe): tick Is Desk User, give it the role PP RSD Integration alone and generate its API key. In Role Permissions, give that role read on Customer Group (the service reads the group tree). Choose the user on the feed registration PP RSD Rules feed.
  3. Stop cashiers moving a customer into another group. The till's edit sheet lets a cashier change a customer's group: a cashier could move a customer into a group that needs nothing. In Customize Form for Customer, set Customer Group's Perm Level to 1, and in Role Permissions give level 1 Write on Customer only to the roles that may move customers (your managers). A cashier's change then leaves the group as it was, and the till still says Customer saved.; a cashier can no longer give a new customer a group either (it takes the default customer group). Check the level 1 rows already there: Sales Master Manager has one by default, and no cashier's role may hold level 1 Write. (Measured with S47; S46 gives the same step.)
  4. Make the sale details the rules name, in Sale Attribute (iVendNext's own screen): one per detail, such as Purchase order number, with a Sequence of its own (no other active detail may hold it), switched on (Active), and no Default Value (a default fills the detail for the cashier, so the check passes with nothing typed). Leave its own Required off: this app decides who must give it. Its Regex Pattern still checks the value's shape for everyone who fills it. A detail switched off, or hidden by a field rule, cannot be filled at the till: take it off the rules first, or every sale for that group is refused.
  5. Write the rules in PP RSD Rule: one per customer group that needs one. Words the cashier reads is optional.
  6. Switch the check on: open Required sale details in Retail Extension Subscription, read what is sent, tick the consent, choose the POS Profiles, and switch it on. Last, press Connect again on the app's record, which sends the integration user's key to the service.

Where it shows. The rules are Desk records the retailer keeps (its System Manager, or a role the retailer gives the rights to in Role Permissions: that role also needs Select on Customer Group, Sale Attribute and POS Profile, or the rule's lookups answer nothing; a System Manager too, where the site removed it). At the till the cashier meets them at Pay.

What leaves the retailer's iVendNext. At Pay, and before each refund the till pays, the priced basket goes to the service's address: each line's item, group, quantity and amounts, the totals, the store, till, POS day and terminal, the cashier's user, the customer and their customer group, the coupons, the payment methods offered, and the sale's details with the values the cashier typed (a purchase order number, a policy number, a phone number); never a card. The service reads the retailer's rule records and its customer group tree with the integration user's key, keeps them one minute, and keeps nothing of a sale. It writes nothing. A hosted copy holds each retailer's check secret and its user's API key from Connect again until the retailer removes the app. Say so on your card.

The files

File What it is
manifest.json the record type PP RSD Rule (one per customer group, named by it) and its tables PP RSD Rule Detail (a Sale Attribute) and PP RSD Rule Profile (a POS Profile), a feed registration (it names the app's one integration user), a help address, and one check, installed switched off; when the service is down the sale goes on, flagged
details.py the pure core: the signature check (verify), the shape check, the retailer's rules (rules_of) and tree (tree_of), the rule that decides (deciding), the details not filled (missing) and the answer (on_check)
service/app.py the service, standard library only: POST /rsd/check, GET /health, and the app template's handshake for many retailers; one handle answers the web server and the function entry (lambda_handler) alike
Dockerfile · app.json the settings file for hosting ("runs": "function": on CitiXsys hosting it is a function, paid per call) and an image for your own server (built from the kit's root)
tests/ the rules, the service over the wire with signed calls (one retailer and many), iVendNext's reads against a stand-in iVendNext, every answer against the published contract, the manifest; canaries.py breaks each guarded rule and expects its test to fail

Run it

python3 -m unittest discover -s samples/s50_required_sale_details/tests -p 'test_*.py'
python3 samples/s50_required_sale_details/tests/canaries.py
python3 samples/s50_required_sale_details/service/app.py samples/s50_required_sale_details/service/.env.local

Settings, one retailer (service/.env.local, never committed): IVN_CHECK_SECRET (handed back at install as pp_rsd:sub:details), IVN_SHOP_URL, IVN_SHOP_KEY (<api key>:<api secret> of the integration user), PORT. RSD_PREFIX names the rule record type (default PP RSD).

Many retailers, hosted

The service is the app template's shape: set IVN_LIBRARY_APP and a store (IVN_SHOPS_TABLE on CitiXsys hosting, else IVN_SHOPS_FILE). Each retailer connects itself after its install and hands over the check's secret; Connect again hands over its integration user's key. Until the key arrives the service answers 503 and the down answer applies. Every call names its retailer (X-iVendNext-Site), and each retailer is answered from its own rules with its own key.

Make it your own

What a partner usually changes:

Before you copy it

Technical notes

Why it is built this way

Option Cost Verdict
Do nothing: tick Required on the Sale Attribute 0 every customer at every till must give it: a walk-in customer asked for a purchase order number
A new rule in Till guard rules ~120 lines and a new rule kind on a listed app its rules are store-wide limits on lines and totals, never keyed on the customer
A manifest only: a field on Customer Group listing its details ~20 lines nothing reads it at the till; a manifest cannot answer a check
A check before payment, answered by a function that refuses while a needed detail is empty ~120 lines of core and service on S46's copy picked: the rule holds at every door that takes money, on the tills the retailer switches it on for, in the retailer's own words; S46's service, its group-tree walk and its cache reused

The answer, exactly

Bounds, stated

Needs this serves

This page in the kit