Skip to main content

Building an integration

DRE has no vendor- or plugin-specific contract. Whatever your channel — a till, a webshop checkout, a kiosk, a CRM, an ERP — your integration (the "adapter") has the same three jobs:

  1. Map your basket → EvaluateRequest and call POST /pos/v2/evaluate.
  2. Map EvaluateResponseV2 → your UI / receipt (discounts, totals, free items, recommendations).
  3. Commit at checkout with POST /pos/v2/confirm, then reconcile via the side-effects poll.

This page gives the mapping tables, per-channel recipes, and the resilience and performance practices that keep a customer-facing checkout fast and safe.

Request mapping — your basket → EvaluateRequest

Your conceptRequest fieldNotes
Store / registerposGroupCode or posGroupIdAt least one is required. A code is friendlier than a UUID.
Cart line SKUitems[].articleNumberRequired, one entry per line.
Quantityitems[].quantity> 0 sale, < 0 return/refund, 0 rejected.
Unit priceitems[].unitPricePer unit, before discounts. Echoed unverified — you own price correctness.
Barcodeitems[].eanOptional.
Product groupitems[].articleGroupIdEnables article-group promotions.
Stable line iditems[].lineReferenceRecommended. Lets you correlate response lines back to your cart lines.
Shoppercustomer.customerId, loyaltyCardNo, loyalty.{tier,points}Optional; required for loyalty/customer-gated promotions.
Voucherscoupons[] = [{ "code": "…" }]Object array, not bare strings.
Sales channelchannele.g. IN_STORE, ONLINE, MOBILE — drives CHANNEL conditions.
PaymentpaymentInfo.{paymentMeans,cardType,cardNumber}For payment-method promotions.

Remember the whole body is wrapped in a top-level request object:

{
"request": {
"header": { "transactionId": "TXN-1" },
"posGroupCode": "STORE-001",
"channel": "ONLINE",
"items": [
{ "lineReference": "L1", "articleNumber": "ART-1001", "quantity": 2, "unitPrice": 89.99 }
],
"customer": { "customerId": "CUST-4711" },
"coupons": [ { "code": "WELCOME15" } ]
}
}

Response mapping — EvaluateResponseV2 → your UI

Response fieldUse it to render
lineItems[].discounts[]Per-line discount rows / strike-through pricing.
lineItems[].lineNetThe net price to display and charge for the line.
lineItems[].isFreeItemMark an in-cart line as a free gift.
grantedItems[]Free items to add to the cart/receipt (they were not scanned).
totals.grandTotalThe amount due.
totals.savingsSummary.totalSavingsA "you saved X" banner.
totals.savingsSummary.loyaltyPointsEarnedPoints-earned messaging.
recommendations[]Upsell / near-miss hints — render via the hint catalog.
appliedCoupons[] / invalidCoupons[]Coupon accept/reject feedback.
budgetLimitedPromotions[]Explain why an expected promotion did not apply.

Because lineItems is strictly 1:1 with your input items (correlated by lineReference), the simplest adapters zip the response back onto the request by position or by lineReference — no matching logic needed.

Per-channel recipes

POS / checkout terminal

  • Call evaluate on every basket change (debounce rapid scans, e.g. 100 ms).
  • Render lineItems[].discounts[] and totals live.
  • At payment, confirm the current iteration, then poll side-effects for coupon redemption and any post-purchase voucher to print.
  • For offline resilience, target a local-store instance; check GET /pos/heartbeat for connectivity and pending-transaction state.

Webshop / e-commerce

  • Use POST /pos/v2/simulate for the cart and mini-cart preview — it is read-only and never consumes budgets or coupons.
  • Run a real evaluate + confirm only at order placement, so budgets and coupons are consumed exactly once.
  • Pass channel: "ONLINE" so channel-gated promotions resolve correctly.

CRM / marketing automation

  • Issue personalized coupons with POST /pos/coupons/issue (no basket needed) — see the coupon lifecycle.
  • Track redemption via the side-effects poll or subscribe to coupon webhooks.
  • Respect the 1000-issuances-per-minute-per-tenant rate limit; batch with customerIds[].

ERP / master-data

  • Push article master data with POST /pos/articles/import (sync) or import-batch (async) — see the wire contract.
  • Read promotion definitions for downstream systems via GET /admin/Promotions (OData V4) — see the Promotions API.
  • Manage budgets through the Admin Service.

Resilience

A promotion engine sits on the critical path of a sale. Design so that a slow or unavailable engine never blocks a checkout.

  • Timeouts. evaluate is synchronous and customer-facing — set a client timeout (a few seconds) and treat a breach as a failure, not a hang.
  • Retries. Retry evaluate on 5xx/timeout with capped exponential backoff (e.g. 3 attempts, 100 ms → 200 ms → 400 ms). Do not blind-retry confirm — poll the side-effects endpoint to learn whether it committed (why).
  • Fallback. After exhausting retries, proceed with no promotions rather than failing the sale. Log it and alert on a threshold so the gap is visible.
  • Cache master data. For display or offline use, cache active promotions read via GET /admin/Promotions and refresh on a poll interval; do not cache per-customer evaluate results.

Performance budget

evaluate/simulate run in a synchronous, customer-facing context. Target these and alert when you exceed them:

PercentileTargetIf exceeded
P50< 200 msNormal
P95< 500 msMonitor
P99< 1000 msAlert
Hard timeout~5000 msFall back (see above)

Helpers: scope each request to the right posGroupCode; keep baskets to the lines actually scanned; reuse a pooled HTTP connection; cache and reuse the auth token (BTP) until near expiry.

Correlation & idempotency

  • Assign a stable lineReference to every cart line and keep it for the life of the basket.
  • Carry the (transactionId, transactionCounter) tuple from evaluate into confirm — the server assigns the counter; you echo it back. See the lifecycle.
  • GET reads are safe to retry. confirm and coupon redeem are not idempotent — reconcile via the side-effects poll instead of replaying.

See also