iVendNextDevelopers My apps

Example 1: a chain's own loyalty system

Part 1 of 3 · 1c. How the data travels: loyalty and telecom plan integrations

A retail chain runs its own loyalty system, outside iVendNext: its members, their points and today's offers live there. On every sale the till should show the customer's points and offer, the member's reward should enter the sale before payment, and after payment the chain's loyalty system should hear what was earned.

Back: the overviewNext: Example 2

Built on sample S22 (customer-owned loyalty). On its own, S22's service is the loyalty engine: its tiers, cash back and ledger are kept as records in the chain's iVendNext, and it calls nothing outside. Its opt-in adapter (service/outside.py) adds the outside hop: when the chain's settings record names its loyalty system's address, the Member key also asks that system for the customer's points and offer, and each sale's earning is posted to it after payment. With the address empty, S22 behaves exactly as shipped. The tier the till prices is S22's own: its daily job moves each member to the customer group the year's spend reaches. The adapter shows the chain's own points and offer and keeps the chain's system told; it does not move a customer's group to a tier the chain's system decides (that would be one more call, from your service to the chain's iVendNext, on S22's pattern). So in this example the reward that reaches the sale is S22's own: the tier's discount (a promotion the till prices) and Member cash spent from S22's ledger. The chain's points and today's offer are shown to the cashier, not applied. To let the chain's own system decide the reward, compose the same doors: your service moves the customer's group to the tier the chain's system names (so the till's promotion prices it), and the wallet's what may this member spend asks the chain's system for its balance. Both are calls you add on S22's pattern; no new door is needed.

The steps

  1. A sale is rung up. The cashier puts the customer on the sale.
  2. The reward enters the sale before payment, priced by the till itself. The member's tier is a customer group, and each tier's discount is an ordinary promotion for that group, which the till prices as the lines go on, online or offline. The customer sees the discount on the sale. No call is made for it.
  3. The cashier presses Member. The till asks your service, which asks the chain's loyalty system for the customer's points and today's offer, and the till shows them in a message: the tier, the cash back, what reaches the next tier, the customer's points and the offer. The message adds nothing to the sale.
  4. At Pay, the member may spend the cash back (Member cash): the till's payments app asks your service, through the wallet door, what this member may spend on this sale, and takes it as a payment.
  5. After payment, the event feed tells your service. It writes the cash back earned to the ledger (a record in the chain's iVendNext, named by its unique key) and posts the earning to the chain's loyalty system, once. A return or a cancellation later takes back its share, in the ledger and in the chain's loyalty system alike.

The picture

Example 1, a chain's own loyalty system, as a sequence across four parties: the till, the chain's iVendNext, your service (sample S22) and the chain's own loyalty system outside iVendNext. Before payment: the cashier puts the customer on the sale, and the till prices the member's discount itself, from a promotion for the customer's group. The cashier presses Member: the till sends the sale's lines and customer, with no price; the chain's iVendNext calls your service at POST /lyl/member, signed with this chain's own secret; your service reads the member, the ledger and the settings with the app's own key; it asks the chain's loyalty system at POST /v1/members/lookup with this chain's own loyalty account; the customer's points and offer come back, and the till shows them in a message, adding nothing to the sale. At Pay, the till's payments app asks your service through the wallet door what the member may spend. After payment, your service pulls the sale from the event feed, writes the Earn row named by its unique key, and posts the earning to the chain's loyalty system once, with an Idempotency-Key.

# From To Endpoint Data that crosses Signed with Kept where
1 Your service The chain's iVendNext POST /api/method/ivendnext_mpos.api.feed.events (the event feed's door, pulled every 30 seconds or each minute) the chain's new customers, sales and returns, as events the app's own user's key (Authorization: token …) your note of handled events (a cache); the records stay in the chain's iVendNext
2 The till The chain's iVendNext the till's own call when the cashier presses Member the sale's lines, the customer the cashier's session nothing
3 The chain's iVendNext Your service POST https://<your service>/lyl/member (a remote till key) sale_ref, store, POS Profile, company, currency, the cashier's login, the customer's id, each line's item code, quantity and unit. No price, no payment X-iVendNext-Signature: HMAC-SHA256 with this chain's key secret, made at install nothing
4 Your service The chain's iVendNext GET /api/resource/PP LYL Member/<customer>, the ledger and plans lists, GET /api/resource/PP LYL Settings/Default the member, the balance, the plans, the chain's loyalty system's address and key the app's own user's key the chain's iVendNext (its own records)
5 Your service The chain's loyalty system POST <its address>/v1/members/lookup (the stand-in's names; a real system has its own) the customer's id this chain's own loyalty key, read in hop 4 the loyalty system
6 Your service The till (through the chain's iVendNext) the answer to hop 3 {"message": {"title", "body", "level"}}: the tier, the cash back, the customer's points, the offer — (the answer to a signed call) shown on the till; nothing on the sale
7 The chain's iVendNext Your service POST https://<your service>/paygw/1/preview and /authorise (the wallet door, Member cash) the member, the amount due, the payment's reference the wallet's own secret a Redeem ledger row in the chain's iVendNext
8 Your service The chain's iVendNext hop 1's feed brings sale booked; GET /api/resource/POS Invoice/<sale>, then POST /api/resource/PP LYL Ledger the sale's totals and lines; the Earn row, named POS Invoice:<sale>:earn the app's own user's key the chain's iVendNext (the ledger)
9 Your service The chain's loyalty system POST <its address>/v1/earn with Idempotency-Key: POS Invoice:<sale>:earn; a return or a cancellation posts what it takes back, under its own row's key (…:<return>:return, …:<sale>:cancel) the customer, the sale, its value, the cash back earned (negative for a return or a cancellation), the currency this chain's own loyalty key the loyalty system

The member's discount (step 2) crosses no hop: it is a promotion in the chain's iVendNext, priced by the till.

The loyalty account for each chain

The chain keeps its loyalty system's address and key in its own iVendNext, in the app's settings record (PP LYL Settings, record Default: Your own loyalty system's address and key). Only the System Manager and the app's own role read that record; a cashier never does. Your service reads it at the moment of each call, with the key of the chain whose signature the call carried, at that chain's own address.

Why one chain's account is never used for another: your service never chooses an account. The call names its chain and is signed with that chain's secret; the chain's record in your shops store gives that chain's address and key; that key can only read that chain's settings, which hold that chain's loyalty account. There is no path from a call of chain A to chain B's account.

Two chains, same app, same second

Two cashiers at two chains press Member in the same second. They are two separate calls, answered by two copies of your service at once (a hosted function runs up to 10 copies). What differs per call:

Chain A's press Chain B's press
The site the call names (X-iVendNext-Site) chain A's chain B's
The secret that signs it chain A's Member key secret chain B's
The key your service reads iVendNext with chain A's app user's key chain B's
The loyalty account used chain A's (its own settings) chain B's (its own settings)
The address the answer goes back to chain A's till, as the answer to chain A's call chain B's till

What the service holds between calls: nothing about a sale or a member. It keeps the shops store (one row per chain: its address, the secrets its install handed over, its app user's key) and each chain's note of handled feed events, in a folder per chain. Everything else is read again at each call. S22's own tests hold this with two chains sharing a customer's name (One service for many retailers).

Run the loyalty example yourself

The tests run the chain's loyalty system as a stand-in web server on your laptop:

python3 -m unittest discover -s samples/s22_customer_loyalty/tests -p 'test_*.py'

tests/test_adapter.py runs the chain's loyalty system as a stand-in web server on your laptop (tests/standin_loyalty.py) and drives the Member key and the earning through it: switched off, nothing is called; switched on, the customer's points and offer appear and each sale is posted once; with the loyalty system down, the key says so and the feed retries. The canaries break each rule and expect a test to fail:

python3 samples/s22_customer_loyalty/tests/canaries.py test_adapter

To watch the hops against your own test site: start the stand-in (port 8156, key chain-loyalty-key), put http://127.0.0.1:8156 and that key in PP LYL Settings (Your own loyalty system's address and key), and run S22's service in local mode (IVN_LOCAL=1: plain http is accepted only to this machine, and only then), as its README says:

python3 samples/s22_customer_loyalty/tests/standin_loyalty.py
IVN_LOCAL=1 python3 samples/s22_customer_loyalty/service/app.py samples/s22_customer_loyalty/service/.env.local

Back: the overviewNext: Example 2

This page in the kit