Skip to main content

Integration Checklist

Read first: QR Code Loyalty Guide · Phone Number Guide · Kiosk and POS Configuration · Message & Error Codes

This is the normative go-live gate for an In-Store Loyalty API integration. Every item is objectively verifiable — you either did it or you did not — and LoyaltyPlant runs an equivalent checklist during certification. Work through it against the latest protocol version, 1.7.

Items marked (conditional) apply only if you implement that capability; skip them otherwise and note the skip in your test report.

1. Contract conformance

  • All requests target versioned paths for the version you certify against — /one-time-salt/1.7, /qr-code/1.7, /order-closed/1.7, /refund/1.7.
  • All requests are HTTP POST with Content-Type: application/json.
  • Request and response payloads validate against the schemas in cloudvalidator-api.yaml — field types, required fields, and enum values match (the spec lint passes).
  • establishmentId is configurable per outlet on the POS side, not hard-coded.

2. Authentication

  • A fresh one-time-salt is fetched immediately before each authenticated call (qr-code, order-closed, refund) — no salt pooling or caching.
  • AuthorizationToken is computed as SHA256(apiPrivateKey + salt) in lowercase hex (v1.4+); the key comes first and the request body is not part of the hash.
  • SaltId and AuthorizationToken headers are sent on every authenticated call; one-time-salt itself is sent with no auth headers.
  • A salt is never reused; a reused or unknown SaltId returns HTTP 403 {"message":"Token expired."} and is handled as an auth error, not a loyalty outcome.
  • HTTP 403 responses are handled distinctly from business outcomes (recompute token with a fresh salt; surface a clear operator error).
  • A request with a wrong/unknown apiPrivateKey is verified to fail with HTTP 403 (and surfaces a clear "LP communication error").

3. QR accrual flow

Points are only credited after order-closed; the scan itself just pre-validates. The customer scans their QR code at the self-service kiosk, or the cashier scans the customer's QR code at the cash register / POS terminal. Verify the accrual path end to end, including the cases where the customer changes the order after scanning.

  • A QR code (or 5-character short code) read from the LoyaltyPlant app — scanned by the customer at the kiosk, or by the cashier at the cash register / POS terminal — is sent in the qrCode field of qr-code.
  • orderId and the full posInfo (establishmentId, terminalId, terminalCurrentTimestamp, terminalTimeZone) are sent with every qr-code request.
  • The bonus card can be read before, during, and after items are added to the order, with correct results in each case.
  • Scanning the same card twice into one order credits points only once.
  • A repeated identical QR string is rejected (504/505), and a non-LoyaltyPlant QR is rejected (503); the returned message is displayed in both cases.
  • If all items are removed after a scan, the order closes as a valid zero/empty transaction without error.
  • Loss of connectivity during scan or closure is handled gracefully (operator sees a clear "no connection" state; the order is preserved for retry when connectivity returns), including the case where the POS app is closed and reopened mid-order.
  • An anonymous app user (empty profile name/email) scans and accrues points correctly.

4. QR redemption flow

A reward is a menuItems line returned with a 100% discount; redemption only settles correctly if that exact item and discount are echoed back at closure (the "reward echo" rule). Redemption is triggered the same way as accrual — the customer scans their QR code at the self-service kiosk, or the cashier scans the customer's QR code at the cash register / POS terminal. Verify both the redeem and the change-of-mind paths.

  • Returned menuItems (reward items) and their modifiers are added to the order.
  • Item-level and order-level discounts (type = percent / amount / fixedPrice, with discountId and value) are applied.
  • Reward echo: each reward item and its accompanying 100%-discount (the discountId LoyaltyPlant returned) is echoed back, unchanged, in the order-closed order contents.
  • If a returned payment is present, it is registered on the order and its transactionId is stored for the closure.
  • Removing a reward before closure is handled: the reward is omitted from order-closed and LoyaltyPlant returns the reward (verified the points come back).
  • Two rewards in one order, and rewards for two different customers in one order, settle correctly.

5. Phone-number flow (conditional)

This is the phone number-based flow. The customer enters their number on the self-service kiosk, or the cashier enters it at the POS / cash register.

  • After the payment screen, the customer can enter a phone number on the self-service kiosk — or the cashier can enter it at the POS / cash register; qr-code is sent with phoneNumber (and optional clientName, subscribedToPushNotifications) and no qrCode.
  • The phone-entry screen — on the kiosk and on the POS / cash register alike — is configurable: phone mask (non-mandatory, leading + non-deletable), marketing texts/banners per language, and legal-consent section.
  • 509 (wrong phone format) and 510 (flow disabled) are handled — re-prompt or fall back to QR-only — whether the number was entered by the customer at the kiosk or by the cashier at the POS / cash register.

6. order-closed correctness

This is the request that credits points and settles rewards, so its contents and exactly-once delivery are the highest-risk items in the integration.

  • order-closed carries the complete final order — all paid items, all reward items (with their discount), all order-level discounts, all payments, and waiter.
  • orderTotal is reported without subtracting the points payment (the points payment is listed in payments); only reward-item value is netted out.
  • For a LoyaltyPlant payment, payments[i].transactionId equals the transactionId from the qr-code response, and every points-payment transaction ID is unique.
  • order-closed is sent exactly once per order; a duplicate is detected and not resent. A repeated order-closed is not a 506/ORDER_ALREADY_CLOSED (that is the processQrCode scan-time code) — it returns HTTP 200 with accepted: false and errorCode: 0 ("document not found or not in open state"), which on a retry confirms the first closure landed; branch on accepted.
  • The closing call is guaranteed in every code path; an order closed without it auto-annuls (validated-but-unused rewards return automatically after about a day).
  • The order-closed response is processed by branching on accepted (the authoritative success signal — not errorCode, which is 0 on both accepted and rejected closures): read pointsReward/pointsSpent on success, and errorText for the rejection reason when accepted: false.

7. Refunds

  • On a full order cancellation/refund, refund is called with establishmentId and orderId; LoyaltyPlant returns used rewards and writes off accrued points.
  • Partial refunds are not attempted — only the entire order can be cancelled.
  • The refund response is processed by branching on accepted (the authoritative success signal — not errorCode, which is 0 on both accepted and rejected refunds); when accepted: false, the reason is in errorText.

8. Messages & errors

  • Every messages[] entry from qr-code is displayed to the cashier / on the kiosk screen — none are swallowed.
  • HTTP 200 is never treated as unconditional success: a rejection is detected by accepted: false on qr-code, order-closed, and refund alike — not by errorCode, which stays 0 on both accepted and rejected order-closed/refund (per the golden rule in Message & Error Codes).
  • Per-code handling follows the Message & Error Codes recommendations (retry vs. show vs. escalate).
  • A wrong/non-existent establishmentId is handled (operation fails cleanly with a surfaced error, no crash).

9. Configuration (conditional — POS/kiosk config fetch)

  • If the integration fetches outlet configuration from LoyaltyPlant, it reads apiUrl, apiVersion, establishmentId, password, discountId, and flow from the config response and applies them per outlet.
  • The sales-outlet identifier and the 100%-discount ID used for rewards are configured to match the values registered with LoyaltyPlant.
  • The outlet is verified to appear correctly in the LP CRM after a successful scan and closure.

10. Onboarding & certification

LoyaltyPlant verifies the integration before it goes live; this section mirrors that verification process so there are no surprises at the gate.

  • LoyaltyPlant is notified about a week ahead of the planned completion date, so the verification slot and test plan can be scheduled; if the date slips, a new one is agreed.
  • The certification test cases are run on your side and a test report is produced against the LoyaltyPlant test plan.
  • Access to your test environment is provided to LoyaltyPlant, which verifies the integration against its test plan; flagged cases are fixed and re-verified until all pass.
  • Once every case passes, LoyaltyPlant considers the integration complete and ready for deployment.
  • The first deployment to a POS terminal happens with LoyaltyPlant representatives present, or is recorded on video and shared with LoyaltyPlant.

Note: Your LoyaltyPlant integration manager schedules the certification run and shares the test plan, test app, test LP CRM access, and certification test cases at kickoff.


← Back to Overview