Skip to main content
Transaction data is what a till captures at the moment of sale. It says when and where the sale happened, what was bought at what price, how it was paid for, and which discounts applied. The POS, or the system behind it, sends every finished sale to the Purchase API. That is the call that records it. It is the input that unlocks:
  • Digital receipts
  • Real-time bonus (earn)
  • Challenges (formerly Stamp Cards)
The Purchase API is what records the sale. It stores the transaction and triggers receipts, bonus and challenges. It is not the Discount API, which only calculates discounts during checkout and stores nothing. Even if you use the Discount API at the till, you must still send the finalized transaction here. See POS integrations for where this sits in the lifecycle.

How it works

The API is built for fast ingestion, so the till is not left waiting. It validates the payload and stores it. Then it publishes a PurchaseLoadedEvent, asynchronously, to the message broker topics that other services subscribe to. Bonus calculation and digital receipts are among them. Purchases with and without a customer are published to separate topics. If you send a loyaltyCardId without a memberId, the service resolves the customer for you from the Loyalty Card service before publishing. The POS streams sales data to the Purchase Load API, which feeds bonus calculation, receipts and the challenge service, and from there the bonus account shown in the admin portal and the app The POS sends the sale once; Lobyco fans it out to bonus, receipts and challenges, and the result reaches the admin portal and the app

Integration options

Persist before you send. Write the transaction to local storage first, then transmit. A network blip or a service outage then costs you a retry rather than a lost sale.

Purchases with and without a customer

The presence of memberId in the payload decides how a purchase is treated.
Send everything, not just identified sales. Filtering anonymous purchases out is technically possible but costs you:
  • Lobyco sees only revenue from identified customers, so the business context is incomplete
  • Analytics degrade — some products, such as Self Checkout fraud detection, rely on whole-basket analysis
  • Power BI reports comparing identified and anonymous behaviour become impossible

Validations

Lobyco validates the payload before it stores anything, and rejects a purchase that breaks any rule below. Every payload must satisfy all of them.

Required fields

Checkout type

The optional checkoutType records which channel the sale came through.

Products

Each product must carry id, sequenceNumber, name, categoryId, quantity, originalPrice and price.
  • price is the amount after product-level discounts; originalPrice is before them
  • Returned or cancelled products carry a negative price
  • price and originalPrice must share the same sign
  • categoryId must not be empty or whitespace

Payment methods

A purchase must carry at least one payment method unless totalAmount is zero. Each payment method must have an amount and a paymentType of Card, Cash, LoyaltyApp or Other.
  • A card payment must carry cardPan, the masked card number
  • A payment in a foreign currency must carry both currencyCode and conversionRate (≥ 0)
  • If the currency matches the purchase currency, conversionRate must be 1
  • rounding is only allowed on Cash

Discounts

Discounts are optional, and each entry must have a totalDiscountAmount. There is no totalDiscount field on the purchase itself — the discounts array holds individual entries. An entry is global when appliedProducts is missing, null or empty, and product-level when it lists product sequence numbers. The distinction changes how the amounts are validated. One product can carry several discounts. The same sequenceNumber may then appear in more than one entry, each with its own name and totalDiscountAmount. The product’s price must reflect the total after all of them, while originalPrice stays untouched. A product at 10.00 with discounts of 0.50 and 4.83 therefore ends up with a price of 4.67. Keeping the discounts as separate entries is what lets each one be reported on individually.
A worked payload for this case is in the Purchases API reference.

Amount calculations

totalAmount must reconcile three ways, and all three must agree. taxNotIncluded is totalTaxAmount when any product has taxIncludedInPrice = false, and 0 otherwise. The first two agreeing is what proves your discounts correctly bridge original prices and actual prices. A small deviation against the payment sum is tolerated.

Other rules

  • memberId, loyaltyCardId, externalDiscountCardId, posId and phone must not be empty or whitespace when provided
  • Tax percentage is a fraction between 0 and 1 — 0.25 for 25%
  • Quantity value must not be 0
  • Shipping totalAmount must be greater than 0
  • DisposalItem unitPrice must be greater than 0

API reference

Endpoint documentation: Purchases.
Last modified on October 8, 2026