iVendNextDevelopers Request a sandbox

A customer's own loyalty programme: the rules outside, the records inside — the recipe

Many retailers run their own loyalty programme: their own tiers, their own cash back, their own rules. This recipe puts such a programme on iVendNext without any code inside the shop: your service holds the rules, the shop holds the records, and the till prices members on its own. It is for an iVendNext Partner, or the CitiXsys Consulting Team, who would otherwise have built an app inside the customer's iVendNext.

Reference implementation: samples/s22_customer_loyalty/ — tiers as customer groups, cash back earned from the event feed with a ledger the shop holds, returns and cancellations taken back exactly, tiers moved daily, a member key on the till, the cash back spent at the till as Member cash (a partner wallet), and a receipt checked before it books (check by workflow).

When to use it. The customer owns the rules and will keep changing them; staff must still see members and cash back on iVendNext's own screens, reports and audit; the till must price members offline. If iVendNext's own loyalty does the job, configure it instead (chapter 4).

If you would have built an app inside the shop, do this instead

Inside the shop, the old way Here
Your own record types for members, plans and a ledger the same record types, declared in your manifest (data, no code); your service writes them through the API
Code that runs when a customer is created or a sale is booked your service, reading the event feed (customer.created, sale.booked, return.booked, sale.cancelled)
A member discount worked out in the till's browser a tier is a customer group with its own promotion (configuration); your service moves the customer between groups
Scheduled jobs inside the shop (credit, upgrade, expiry) your service's own scheduler
A balance panel added to the till's screen a remote till key: your service answers with a message
A check that blocks a back-office document at submit check by workflow: a Verified step only your service can take
A rule checked when a record is saved (a format, a limit) a save rule in your manifest (chapter 7)
A rule checked before the customer pays a check before payment (recipe)
A unit's own barcode recognised at the scan a scan source over your record type (recipe)
A button on a Desk form that runs your logic a Desk action (recipe)
Workflows, notifications and numbering on the shop's own documents configuration over the API, with an undo (sample S5's shape)
A patch or override of the product never: request an extension point (chapter 9)
Paying with the cash back at the till the wallet extension point: your service answers the till (a partner wallet at the till)

Steps

  1. Declare the records in your manifest: settings, plans, members and the ledger, each with its permission rows written out (recipes/manifest.md). Give the ledger a unique key field and name each row by it ("autoname": "field:source_key"): the document and the event, such as POS Invoice:<sale>:earn. Declare the feed registration (the record types you follow) and the member key; both install switched off.

  2. Make the one integration user before install (recipes/integration_user.md): one desk user, one licence, one narrow role of this app's own (S22's is PP LYL Integration: a role another app's user also holds would hand that user your Verify step), holding only what your service reads and writes, never Workspace Manager. The sample's apply_config.py prepare does it and writes each right with its reason. Over the API the user needs custom_is_desk_user: 1 (the screen's Is Desk User), and reading a record type also needs read on the records it names.

  3. Configure the tiers. One customer group per tier; one promotion per tier, limited to that group by excluding every other group. Build the exclusions after every group exists, and rebuild them whenever a group is added, or a new group gets every discount (sample S5's rule). Set the plans and settings records.

  4. Switch it on (the shop's administrator): the feed registration's user is your integration user, and each record type it follows ticked on (the shop writes a feed row only for a ticked one; the installer leaves them unticked); the member key's consent; the key on the store's till theme (POS Studio → Apps). A till whose profile wears no theme offers no key.

  5. Read the feed. Pull your events, act on the labels you need, mark the rest done. Write every ledger row named by its key: when the shop answers 409 (the name is taken), the event was already handled — treat it as done. Your own note of handled events is only a cache in front of that.

  6. Get the money right, in one place: Decimal, rounded half-up to the currency's decimals. Earn on what the customer bought: the sale's total less any gift card sold on it, which earns when it is spent, and less the goods' share of a tender discount. Keep the sale's value without its gift cards on the earning row: the tiers count it, and each return is measured against it. A return takes back its share of what the sale earned, never more than is left; the return that completes the sale takes exactly what is left. While the earning is still Pending, reduce it; after it is Credited, write a negative row. A cancelled sale takes back the rest.

  7. Run the daily jobs: credit what the return window has closed on; move each member to the tier the year's spend reaches, by moving their customer group.

    A balance report with opening and closing figures is one more daily job. Your service writes one balance row per member per day for the day that ended (S22's PP LYL Balance, named balance:<customer>:<day>):

    • the opening is that member's last row's closing;
    • the closing is what the member can spend now;
    • the change is the difference.

    A Report Builder report over the rows (S22's PP LYL Member cash by day), filtered by date, reads the opening of the first day and the closing of the last. S22's loyalty.daily_balances, two tests; a second run of the day writes nothing. The limit: the closing is taken when the job runs (the first pass after 02:00 UTC), so a move between midnight and the run counts in the day that ended. A day the job did not run has no row: the next row's opening still follows from the last closing.

  8. Answer the member key: the plan, the balance (and what is pending), and how much more reaches the next tier. Verify the shop's signature over the raw body first (sample S18's verify).

What the shop needs from you, and what it checks

When your service is down, and when the till is offline

Technical notes

Bounds

What was proven where

Needs this serves

This page in the kit