iVendNextDevelopers Request a sandbox

S32 — Coupon rules (a check before payment that refuses a coupon the shop restricted)

Rules a shop adds to its own coupons, checked when the cashier presses Pay. One rule record per coupon, written in Desk; each rule is off until ticked or set:

Rule What the cashier sees
First purchase only "This sale can't be paid" and "Coupon WELCOME10 is for a first purchase only; this customer has bought before. Remove it, or add the new customer." The till always names a customer: before the cashier adds the new one, it is the walk-in customer, who has usually bought before
Only for customers in one group "Coupon STAFF15 is only for customers in Staff. Remove it, or add a customer in that group."

A coupon with no rule, or with its rule switched off, is never refused; a sale with no coupon is allowed without reading the shop. The cashier removes the coupon (or adds the new customer) and presses Pay again. The recipe: recipes/coupon_rules.md.

What the shop does

  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 CPN Rule, the role PP CPN 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 CPN Integration alone and generate its API key. In Role Permissions, give that role read on POS Invoice and Sales Invoice (the first-purchase rule counts a customer's sales): a plain read, not Only If Creator, and give the user no User Permissions, or the count sees only part of a customer's sales and a returning customer can pass as new. Choose the user on the feed registration PP CPN Rules, then press Connect again on the app's record, which sends that user's key to the service.
  3. Write a rule for each coupon to restrict in PP CPN Rule: the coupon, First purchase only, and/or Only for customers in this group.
  4. Switch the check on: open Coupon rules in Retail Extension Subscription, read what is sent, tick the consent, choose the POS Profiles, and switch it on.

Where it shows. The rules are Desk records the shop edits. At the till the cashier meets them at Pay: a refusal in the app's words. A sale the service could not check goes on and is flagged on Sales to review.

What leaves the shop. At Pay, the priced basket goes to the service's address: each line's item, group, quantity and amounts, the totals, the store and till, the cashier's user, the customer and their customer group, the coupons, the sale details the cashier captured and the payment methods offered; never a card. For a coupon with a rule, the service reads the shop's rule records, and for a first-purchase coupon how many submitted sales that customer has, with the integration user's key. It writes nothing. The manifest names that one address; the shop sees it, and the check's consent, on the install screen and on the check's own record before it is switched on.

What is in the folder

File What it is
manifest.json the record type PP CPN Rule (one per coupon, named by it), 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
coupons.py the pure core: the signature check (verify), the shape check, the shop's rules (rules_of) and the answer (on_check)
service/app.py the service, standard library only: POST /cpn/check, GET /health, and the app template's handshake for many shops; 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 shop and many), the shop's reads against a stand-in shop, 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/s32_coupon_rules/tests -p 'test_*.py'
python3 samples/s32_coupon_rules/tests/canaries.py
python3 samples/s32_coupon_rules/service/app.py samples/s32_coupon_rules/service/.env.local

Settings, one shop (service/.env.local, never committed): IVN_CHECK_SECRET (handed back at install as pp_cpn:sub:coupons), 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 the check's secret when it connects and its integration user's key on Connect again; until the key arrives the service answers 503 and the down answer applies. CPN_PREFIX names the rule record type (default PP CPN).

Technical notes

What was proven where

Needs this serves

This page in the kit