iVendNextDevelopers Request a sandbox

S52 — Phone plans from a telecom company (pp_tel), sold with the phone

Start from this sample: python3 tools/manifest/start.py s52_phone_plans --prefix "ACME TEL" copies it into apps/ of your repository under your own prefix (your company's first word, then a short name for the app) and checks it (how).

A phone retailer sells mobile plans from a telecom company at its tills. The plans and their prices are the telecom company's, and they differ from one retailer to the next: each retailer has its own account with the telecom company. This sample links the till to the telecom company through your service, with each retailer's own account, and follows the data the whole way: the cashier sees the plans, the plan's price enters the sale as a line before payment, Pay confirms it with the telecom company, and after payment the plan is activated and its number written back.

When to use it: any link between the till and an outside system that prices something per retailer account (the outside system confirming each customer at Pay) and must hear about the sale afterwards: a telecom company's plans, an insurer's device cover, a utility's connection. Chapter 1c of the guide walks this sample hop by hop (How the data travels).

What happens

  1. The cashier scans the phone and presses Plans. The till sends your service the sale's lines and customer (no prices). Your service reads this retailer's own telecom account from the retailer's settings record, asks the telecom company for the plans that fit this phone and this customer, and answers with a message the till shows: each plan's name, price and code. Nothing is added to the sale.
  2. The cashier adds the chosen plan as its own item (scan its code, or pick it). The plan is an ordinary item of the retailer's (for example PP-TEL-S, in the item group Mobile plans), priced from the retailer's price list. Your service keeps that price list in step with the telecom company's prices for this retailer's account, so the till prices the line itself, offline too, and the customer sees it on the sale before paying.
  3. At Pay, the check confirms the plan. The till's server prices the basket and asks your service; your service asks the telecom company again, for this customer: does the plan fit a phone on this sale, may this customer take it, and is its price still the one on the line? Yes: the payment sheet opens. No: the sale is refused in plain words, for example "M 50 GB is now USD 35.50 with the telecom company, not USD 35.00. In a minute, take the plan off and add it again."
  4. After payment, the plan is activated. The retailer's event feed tells your service the sale was paid. Your service asks the telecom company to activate the plan, with the retailer's site and the sale's line as the call's idempotency key (each plan on its own phone's serial number), and writes the activation number to a PP TEL Activation row, listed under the sale (Phone plans, in its Connections) and under the customer.

The PP TEL Activation list in Desk on the programme's test bench: one row for sale ACC-PSINV-2026-12468, status Activated, activation number ACT-NORTH-00001.

What the retailer does

  1. Install the app (the retailer's administrator, from the install screen). It adds PP TEL Settings, PP TEL Activation (listed under POS Invoice and Customer), the feed registration PP TEL Sales, the till key Plans and the check at Pay Phone plan check. The check and the feed come in switched off; the Plans key comes in ready, with the install's consent, and the store places it on its till in POS Studio > Apps. From POS 2.1.0.667 the install also makes the app's own user, pp_tel@integration.invalid, holding the role PP TEL Integration alone (chapter 7); its key reaches your service when the retailer connects (chapter 14b).
  2. Keep the telecom account in the retailer's own iVendNext. A System Manager makes the PP TEL Settings record named Default: the telecom account and its key (as the telecom company gave them), the item group the plans are in, the price list the plans are sold from, and the store's walk-in customers (each POS Profile's default customer, which the till puts on a sale that has none; a plan is never sold to one). Only the System Manager and the app's own role read this record; a cashier never does.
  3. Give the app's role its rights on the retailer's own records (Role Permissions; a manifest grants rights only on its own record types): PP TEL Integration gets read, write and create on Item Price, and read on Price List, Item and POS Invoice. ⚠ With these, the app's key could change any item price; the service writes only the prices of items in the plans' item group, but give the role to the app's own user alone.
  4. Make the plans as items in the plan item group, each with the telecom company's plan code as its item code, not stock items. The app writes their prices; do not type them.
  5. Switch it on. Tick POS Invoice on the feed registration PP TEL Sales; consent to and switch on the check Phone plan check for the tills that sell plans; place Plans on the till in POS Studio > Apps.

What the cashier sees. Plans on the counter and the tablet: a sheet listing the plans for the phone on the sale, or "No phone on this sale has plans from this telecom company", or "Telecom company not answering … Nothing was added to the sale." The plan as a line with its price. At Pay, the payment sheet, or "This sale can't be paid" with the app's reason. Nothing else changes at the till.

What leaves the retailer's iVendNext. To your service: at each press of Plans, the till key's list (the sale's lines' item codes and quantities, the store, the POS Profile, the cashier's login and the customer's id; no price); at each Pay with the check on, the check's list (the priced basket and the customer); through the feed, each paid sale, read with the app's key. To the telecom company, from your service: the retailer's own account and key, the item codes on the sale, the customer's id, and for an activation the plan, the phone, the phone's serial number when the sale carries one, and the sale's name. Nothing is sent while the till is offline.

The files

File What
manifest.json the settings record, the activation record (named by its sale line, listed under the sale and the customer), the feed registration, the Plans key and the check at Pay
service/plans.py the rules, with no web code: the Plans message, the check's verdict, the activation work and the price changes
service/telecom.py the telecom company's three calls, with one retailer's account at a time: the file you change for a real telecom company
service/app.py the till's two calls (POST /tel/plans, POST /tel/check), the signature check, and many retailers through the app template's handshake
service/worker.py after payment: the feed, the activation, once; and the price list kept in step
service/shop.py the retailer's record API, with the app's key (copied from S22)
standin/telecom_api.py, standin/accounts.json a stand-in telecom company with two accounts and its faults, so the whole flow runs with no network
tests/ the laptop tests, with two retailers calling at the same moment, and canaries.py
app.json, Dockerfile on CitiXsys hosting a function (each press, and a feed pass each minute); on your own server, the container

Run it

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, and the service against a retailer on your laptop (one retailer; its settings in an env file you write, never committed):

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

with IVN_POINT_SECRET (the Plans key's secret the install handed back), IVN_CHECK_SECRET (the check's), IVN_SHOP_URL, IVN_SHOP_KEY (the app's user's key, <api key>:<api secret>), TEL_API=http://127.0.0.1:8154, and PP_STATE (a folder). For many retailers on your laptop, use the app template's local mode (IVN_LOCAL=1, the template's README) with IVN_LIBRARY_APP and IVN_SHOPS_FILE instead.

Many retailers, one service

One service serves every retailer that installs the app. Each press and each check names its retailer (X-iVendNext-Site) and is signed with that retailer's own secret; the service finds the retailer's address and key in its shops store, reads that retailer's telecom account from that retailer's settings record, and calls the telecom company with it. The service keeps nothing between calls but the shops store and each retailer's note of handled feed events, so two retailers pressing Plans at the same second are two separate calls, each answered with its own account and its own prices (tests/test_service.py, TwoRetailersAtOnce). The activation's idempotency key is the retailer's site and the sale's line, so the same sale name at two retailers is never one activation, however a real telecom company scopes its keys.

Before you build on it

Technical notes

Why it is built this way

The calls, exactly

From To Endpoint Signed with
till (the retailer's iVendNext) your service POST /tel/plans (remote till key, returns: message) the Plans key's secret, HMAC-SHA256
till (the retailer's iVendNext) your service POST /tel/check (check/1) the check's secret, HMAC-SHA256
your service the retailer's iVendNext GET /api/resource/PP TEL Settings/Default; the feed's door; GET /api/resource/POS Invoice/<sale>; Item Price reads and writes; PP TEL Activation writes the app's user's key
your service the telecom company GET /v1/plans, POST /v1/offers, POST /v1/activations (the stand-in's) the retailer's own telecom account and key

What was proven where

Needs this serves

This page in the kit