Scope of changes in Phase 2
Agreed scope, unchanged from Version 1 §7 (28 Aug / 4 Sep 2026). Phase 2 keeps
every operation Version 1 allowed; nothing has been removed. Where a row says
“REKOM practice”, it describes how REKOM intends to use the operation, not a
restriction on it.
Directional. Field names, enum values, headers and JSON structures on this page
show which information is exchanged, not the final names. None of them has been
aligned with OnlinePOS yet. REKOM will adopt whatever OnlinePOS has by default for
the order object and its webhooks, and does not require any of these specific names.
Line model (directional)
The request body is OnlinePOS’s own basket object, exactly as OnlinePOS has it, including the discounts already on it. REKOM modifies that object per its loyalty model and returns it; OnlinePOS applies what comes back. The tables below are REKOM’s illustration of the information in that object, grouped by who sets it.Set by OnlinePOS (input)
Identity and quantity fields are echoed verbatim on every line REKOM keeps. The existing-discount fields are input: REKOM may change or clear them when it swaps in a loyalty discount.Set by REKOM (only on applied)
Arithmetic identities
For every line:lineDiscountType: "none" and lineDiscountAmount: 0. A response with status
applied changes the basket in at least one way (a discount, a price update, an
added, removed or split line); if the calculation changes nothing, the response is
unchanged.
Order-level discounts and the legal-discount flag
Agreed 3 Sep 2026 (Google Doc comments on V1 §8). Order-level discounts are
allowed, and REKOM does the splitting onto lines. OnlinePOS’s answer: to register
revenue correctly, all order discounts have to be split on the separate lines, and
because some products, cigarettes for example, are not legally allowed to be
discounted, the partner must do that split correctly. OnlinePOS places order
discounts visually at the bottom of the receipt, as it already does for discount
campaigns.
Discount code
Agreed, follow-up after 7 Oct 2026. Loyalty discounts carry no campaign ID.
Every loyalty discount instead carries a predefined, semantic string that
identifies the catch-all third-party-loyalty discount type in POS
(
lineDiscountCode on lines, discountCode on order discounts). This is the
“general system campaign for third-party loyalty” variant of the 6 Oct 2026
assumption: no per-venue campaign is created, and nothing is provisioned through
the REST API.
Staff discounts that already exist on a line or order keep their campaign reference
(
existingLineDiscountCampaignId, orderDiscounts[].campaignId); that is
OnlinePOS’s existing discount system and is untouched.
Receipt rules
Agreed 17 Aug, 3 Sep and 6 Oct 2026.
- A loyalty line discount prints as a note line directly under the product, with the discount text and the monetary value shown separately. The product’s own receipt text is never changed.
- No new print layouts. Note lines use the existing mechanism.
- A discount is dedicated POS discount data, never a free-text product with price zero (3 Sep 2026).
- Order-level discounts are shown at the bottom of the receipt, as OnlinePOS does
for discount campaigns (3 Sep 2026), through a free-text field that is
informational and not part of reporting (7 Oct 2026). REKOM may fill
receiptFooterText(≤ 200 characters) for this, e.g. “Members 10 %: −13,00 kr.” memberLabel(≤ 40 characters, e.g. “REKOM Member”) may be shown on the receipt header or footer if OnlinePOS has a place for it; otherwise ignored.- Nothing from the response is shown on the customer display or to staff before payment beyond the updated basket total, since the call runs during the payment motion.
Rounding
Proposed — for OnlinePOS confirmation.
Existing discounts: REKOM decides what is swapped
REKOM logic. The basket OnlinePOS sends includes the discounts already on it:
a staff discount, a price discount on a specific product, an order discount. The
REKOM backend decides, per line, what stays and what is swapped. If the loyalty
programme unlocks a larger discount than the one already there, the guest gets the
loyalty discount instead when the basket comes back; otherwise the existing
discount stays and REKOM adds nothing. This logic is strictly REKOM’s. OnlinePOS
simply receives the returned basket and applies it, which follows from the 7 Oct
2026 agreement that the returned basket is authoritative.
- A returned line may carry a different existing discount than it was sent with, or none, because REKOM swapped it out. OnlinePOS’s validation therefore checks arithmetic and product identity, not that discount fields are unchanged.
- A swapped-out staff discount no longer prints; the loyalty note line prints in its place. Nothing is printed twice for the same line.
- Whether a benefit stacks on top of an existing discount or replaces it is a REKOM rule per benefit, not a POS setting. REKOM’s default is replace-if-larger.
- The same applies to order discounts already on the basket: REKOM may keep, change or remove them and re-allocate accordingly.
- If every eligible line already carries a better discount, the response is
unchangedwith reasonall_lines_excluded.
Flags
Validation in OnlinePOS
OnlinePOS validates the returned basket for (V1 §9, unchanged):- Correct schema and the same
calculationIdas the request. - Every
productIdexists on the venue, including added and split lines. - Identity and quantity fields (
lineId,productId,quantity,unitPrice, the flags) are unchanged on every line REKOM kept; split lines’ quantities add up to the original line. Discount fields may differ, because REKOM may have swapped an existing discount. lineDiscountCode/discountCodeequals the agreed catch-all loyalty discount code wherever a loyalty discount is set.- No loyalty discount, allocation or price reduction on a
noDiscountline; no percentage discount on anoPercentageDiscountline. - The sum of
orderDiscountAllocationAmountperorderDiscountIdequals the stated order discount. - The per-line and basket identities above hold; all amounts are integers ≥ 0;
the basket total equals the sum of
orderLinePrice; total ≥ 0.
failed (the sale itself completes), and the sale event carries
failureReason: invalid_response.