iVendNextDevelopers My apps

Example 2: a phone retailer's mobile plans from a telecom company

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

A phone retailer sells mobile plans from a telecom company. The plans and their prices are the telecom company's, and each retailer has its own account with it, at its own prices. The cashier must see the plans for the phone being sold, the plan's price must be a line on the sale before payment, the telecom company must confirm the plan for this customer before money moves, and after payment the plan must be activated and its number written back.

Back: Example 1Next: What both outside systems share

Built on sample S52 (phone plans from a telecom company), with a stand-in telecom company that runs on your laptop.

The steps

  1. The cashier scans the phone and opens the plan sheet (the Plans key).
  2. Your service asks the telecom company's API for the plans and prices for that phone and that customer, with this retailer's own telecom account, and the till shows them. Nothing is added to the sale.
  3. The cashier adds the chosen plan as its own item, priced from a price list your service keeps in step with the telecom company. No till door adds a priced line by itself: the till key, the capture at the scan and the partner page carry no money, and the price door only takes money off. So the plan is an ordinary item of the retailer's, and its price comes from the retailer's own price list, written by your service from the telecom company's prices for this account.
  4. At Pay, the app's check confirms the plan and its price with the telecom company for that customer: the plan fits a phone on the sale, the customer may take it, its price still holds. If they no longer hold, the sale is refused in plain words, and the cashier reads why. A sale the cashier rang with no customer reaches the check with the store's walk-in customer on it (the till puts its POS Profile's default customer there); the retailer names its walk-in customers in the app's settings, and a plan for one is refused: "A phone plan needs the customer on the sale. Add them, then press Pay."
  5. After payment, your service activates the plan with the telecom company and writes the activation number back: to a PP TEL Activation row of the app's own, named by the sale and its line, linked to the sale and the customer. Staff find it in the PP TEL Activation list, filtered by Sale (below), and in the sale's and the customer's Connections, Phone plans, where those forms open for them.

The PP TEL Activation list in Desk on the programme's test bench: one row, ACC-PSINV-2026-12468:2, for sale ACC-PSINV-2026-12468, status Activated, activation number ACT-NORTH-00001, written by the service from the event feed a minute after the sale was paid.

The picture

Example 2, a phone retailer and a telecom company, as a sequence across four parties: the till, the retailer's iVendNext, your service (sample S52) and the telecom company. Before any sale, on each pass, your service reads the plans from the telecom company with this retailer's own account at GET /v1/plans and writes their prices to the retailer's price list as Item Price rows. At the till: the cashier scans the phone and presses Plans; the till sends the sale's lines and customer, no price; the retailer's iVendNext calls your service at POST /tel/plans, signed with this retailer's Plans secret; your service reads the retailer's own telecom account from PP TEL Settings with the app's key; it asks the telecom company at POST /v1/offers for the plans for this phone and customer; the till shows them in a message and nothing is added. The cashier adds the chosen plan's item, priced from the list. At Pay, the retailer's iVendNext sends your service the priced basket at POST /tel/check; your service asks the telecom company again whether the plan is still offered to this customer at this price, and answers allow or refuse in plain words. After payment, your service pulls the sale from the event feed, activates the plan at POST /v1/activations with the retailer's site, the sale and its line as the Idempotency-Key, and writes the activation number to a PP TEL Activation row listed under the sale.

# From To Endpoint Data that crosses Signed with Kept where
0a Your service The telecom company GET /v1/plans (each pass: a minute on hosting) the account's plans: code, name, price, the phones each fits this retailer's telecom account and key the telecom company
0b Your service The retailer's iVendNext GET and PUT/POST /api/resource/Item Price on the settings' price list each plan's price, where it differs the app's own user's key the retailer's price list
1 The till The retailer's iVendNext the till's own call when the cashier presses Plans the sale's lines, the customer the cashier's session nothing
2 The retailer's iVendNext Your service POST https://<your service>/tel/plans (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 HMAC-SHA256 with this retailer's Plans secret nothing
3 Your service The retailer's iVendNext GET /api/resource/PP TEL Settings/Default the retailer's telecom account and key, the plans' item group, the price list the app's own user's key the retailer's iVendNext
4 Your service The telecom company POST /v1/offers every item code on the sale (the telecom company says which are phones), the customer's id this retailer's telecom account and key, read in hop 3 nothing
5 Your service The till (through the retailer's iVendNext) the answer to hop 2 {"message": …}: each plan's name, price and code — shown on the till; nothing on the sale
6 The till — the cashier adds the plan's item the plan, priced from the price list (hop 0b) — the sale, as a line
7 The retailer's iVendNext Your service POST https://<your service>/tel/check (the check before payment, check/1) the priced basket: each line's item code, item group, quantity, unit, price, amount, discount and captured values; the sale's totals; the customer and customer group; the store, POS Profile, POS day and terminal; the cashier's login; the price list, coupons and the tenders offered; the currency. No card data HMAC-SHA256 with this retailer's check secret the retailer's iVendNext stores the answer against the basket's fingerprint (your service keeps nothing): a basket changed after it is asked again
8 Your service The telecom company POST /v1/offers every item code on the sale other than the plans (the telecom company says which are phones), the customer's id this retailer's telecom account nothing
9 Your service The retailer's iVendNext the answer to hop 7 {"verdict": "allow"} or {"verdict": "refuse", "reason": "…"} — the till: the payment sheet, or the reason
10 Your service The retailer's iVendNext the event feed's door, then GET /api/resource/POS Invoice/<sale> sale booked; the sale's lines, item groups, serial numbers, customer the app's own user's key your note of handled events (a cache)
11 Your service The telecom company POST /v1/activations with Idempotency-Key: <retailer's site>/<sale>:<line> the plan, the phone, its serial number (each plan its own phone's, on a line of several), the customer, the sale's name this retailer's telecom account the telecom company
12 Your service The retailer's iVendNext POST, then PUT /api/resource/PP TEL Activation/<sale>:<line> Pending, then Activated with the number (or Failed with the telecom company's sentence) the app's own user's key the retailer's iVendNext, under the sale

The telecom account for each retailer

The retailer keeps its telecom account and key in its own iVendNext, in the app's settings record PP TEL Settings (record Default), with the item group its plans are in and the price list they are sold from. A System Manager types them in Desk. Only the System Manager and the app's own role (PP TEL Integration) read the record; a cashier never does. The key is an ordinary text field, readable whole by those two roles: the app adds no encryption of its own. Your service reads it at the moment of each call (hop 3) with the key of the retailer whose signature the call carried, at that retailer's own address.

This is the route proved on the programme's test bench (run pp-20261028T170105Z-24298, the Technical notes): the app's own user, made by the install and holding only its own role, read the account and key whole through the record API, and a cashier could not read the record. Two routes that look shorter do not work: a password field read through the record API comes back masked, and reading a password whole (get_password) is for a System Manager, which the app's own user never is. The secrets the install hands your service at connect are the install's own (the till key's, the check's and the app user's key); a retailer cannot add its telecom key to them.

Why one retailer's account is never used for another: your service never chooses an account. The signature covers the call's body; the site header only picks which chain's secret checks it, so a call that names another chain's site fails the check. The account comes from the settings record, read with the key of the retailer the call's signature matched, at that retailer's address. The activation's idempotency key starts with the retailer's site, so the same sale name at two retailers is never one activation, however a telecom company scopes its keys.

Two chains, same app, same second

Two chains use the same telecom app in the same second. Chain A's cashier and chain B's cashier each press Plans. Chain A's call names chain A's site and is signed with chain A's secret; chain B's names chain B's site and is signed with chain B's secret. Two copies of your one service answer them at once. Each copy finds the chain in its shops store by the site the call names, checks the signature with that chain's own secret, reads that chain's telecom account from that chain's own settings record with that chain's key, and calls the telecom company with that account. Chain A gets account A's plans and prices back on chain A's till; chain B gets account B's on chain B's. The service keeps nothing between calls but the shops store, one row per chain.

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 Plans secret chain B's
The key your service reads iVendNext with chain A's app user's key chain B's
The telecom account used account A, from chain A's own settings account B, from chain B's 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: the shops store (one row per chain: on CitiXsys App Hosting the app's own encrypted table, on your own server a file only the app's user reads) and each chain's note of handled feed events. No account, price or sale is kept: a retailer that changes its telecom account in Desk is answered with the new one at its next press. S52's tests run two retailers at the same moment, 24 calls on threads released together, and check that every answer carries only its own account's prices and that the telecom company saw each account exactly as often as its own retailer called (tests/test_service.py, TwoRetailersAtOnce).

Run the telecom example yourself

The tests start the stand-in telecom company as a web server on your laptop and call the service the way the till signs its calls:

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

The stand-in telecom company on its own (port 8154, two accounts, standin/accounts.json), then the service against your own test site, one retailer (the env file's names are in the sample's README):

python3 samples/s52_phone_plans/standin/telecom_api.py
python3 samples/s52_phone_plans/service/app.py samples/s52_phone_plans/service/.env.local

For two retailers on your laptop, run the service in the app template's local mode (IVN_LOCAL=1, the template's README) and connect two local sites.

Back: Example 1Next: What both outside systems share

This page in the kit