☰ Meniu · POS și case de marcat
Ghiduri

POS și case de marcat

Tot ce îi trebuie unei case de marcat: găsește oaspetele, aplică bonul, folosește cashback-ul, tratează returul și emite carduri cu numerele proprii ale casei.

Fluxul obișnuit la tejghea: casierul scanează cardul din wallet al oaspetelui (sau introduce telefonul), casa afișează soldul, oaspetele decide dacă plătește o parte din bon cu cashback, bonul se închide și casa ni-l trimite. Cardul de pe telefonul oaspetelui se actualizează în câteva secunde.

1. Găsește oaspetele

GET /v1/members/{member} acceptă orice are casa: numărul cardului, conținutul codului de bare, telefonul în format internațional sau id-ul nostru de membru. Codifică telefonul pentru URL (+ devine %2B).

curl
# by the number on the card (or the barcode the scanner read)
curl https://loyaltyfy.io/api/v1/members/2000111703779 \
  -H "Authorization: Bearer sk_test_..."

# by phone, with fresh add-to-wallet links
curl "https://loyaltyfy.io/api/v1/members/%2B37366791102?expand=wallet" \
  -H "Authorization: Bearer sk_test_..."

Arată-i casierului, din obiectul membru, ce contează pentru program:

program.mechanicAfișează
cashbackcashback.balance și suma maximă care poate fi folosită: cea mai mică dintre sold și redeem_max_percent din bon.
stampsstamps.current din stamps.required și stamps.rewards_available, dacă oaspetele are un produs gratuit care îl așteaptă.
visit_discount, vipdiscount.percent și discount.tier: aplici tu această reducere pe bon.

2. Ce conține codul de bare al cardului

Localul alege formatul codului de bare în Dezvoltatori → Codul de bare al cardului, ca să corespundă cu ce citește scanerul casei:

FormatCodul de bare conțineFolosește când
QR cu numărul carduluicard_number, de exemplu 2000111703779Scanerul citește QR, iar casa caută după numărul cardului. Recomandat.
Cod de bare (Code 128) cu numărul carduluiAcelași număr, ca un cod de bare liniar, tipărit sub bareScanere laser mai vechi, care nu citesc QR.
QR pentru casa LoyaltyfySeria noastră internă (un UUID)Implicit. Îl citește doar aplicația de casă Loyaltyfy; un POS poate găsi oricum oaspetele după telefon.

Schimbarea formatului actualizează și cardurile aflate deja în wallet-urile oaspeților; nimeni nu trebuie să reinstaleze nimic. Indiferent de format, trimite valoarea scanată ca atare la GET /v1/members/{member} sau la POST /v1/scans, care elimină și prefixele scanerului și sfârșiturile de rând.

3. Emite carduri cu numerele proprii ale casei

Dacă casa are deja un sistem de carduri, păstrează-i numerele. Înscrie oaspetele cu card_number setat la numărul din casă; cardul din wallet afișează acel număr și îl codifică în codul de bare, așa că scanerul găsește oaspetele și în baza de date a casei. Numerele sunt unice per local: un duplicat primește 409 card_number_taken. Ca să atribui mai târziu un număr din casă unui membru existent, folosește PATCH /v1/members/{member} cu card_number.

curl
curl https://loyaltyfy.io/api/v1/members \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7d1b2c4e-enrol-000123" \
  -d '{
    "phone": "+37366791102",
    "first_name": "Ion",
    "last_name": "Popescu",
    "card_number": "2000111703779",
    "language": "ro",
    "consent": true
  }'

Răspunsul conține wallet.apple_url și wallet.google_url. Tipărește-le ca QR pe bon, trimite-le prin SMS sau afișează-le pe ecranul pentru client: oaspetele deschide linkul și cardul se adaugă pe telefon.

4. Aplică bonul

Trimite fiecare bon închis o singură dată, după plată. amount este bonul întreg, înainte de cashback; redeem_amount este partea plătită de oaspete cu cashback. Aplicăm regulile programului (bon minim, plafon de folosire, niveluri) la fel ca ecranul de casă al localului.

curl
curl https://loyaltyfy.io/api/v1/transactions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CHK-2026-000123",
    "member": "2000111703779",
    "amount": 25000,
    "currency": "MDL",
    "register_id": "POS-1",
    "closed_at": "2026-10-06T18:17:00+03:00",
    "line_items": [
      { "sku": "1001", "name": "Espresso", "quantity": 2, "unit_price": 4000, "category": "Coffee" },
      { "sku": "2005", "name": "Cheesecake", "quantity": 1, "unit_price": 17000, "category": "Desserts" }
    ]
  }'
Răspuns · 201
{
  "object": "transaction",
  "id": "f8acbb16-0b3b-4afc-acbb-e5eabd4dd072",
  "external_id": "CHK-2026-000123",
  "status": "completed",
  "amount": 25000,
  "redeem_amount": 0,
  "cashback_earned": 1250,
  "stamps_earned": 0,
  "reward_unlocked": false,
  "net_amount": 25000,
  "currency": "MDL",
  "register_id": "POS-1",
  "closed_at": "2026-10-06T15:17:00+00:00",
  "livemode": false,
  "created_at": "2026-10-06T18:17:17.921+00:00",
  "voided_at": null,
  "member": {
    "object": "member",
    "id": "6dacfe0a-2500-44c6-a37c-05c660b093b4",
    "card_number": "2000111703779",
    "cashback": { "balance": 1250, "currency": "MDL", "earn_rate": 5, ... },
    ...
  }
}
external_id trebuie să fie unic pentru toate casele localului. Dacă numerele bonurilor o iau de la capăt pe fiecare casă sau în fiecare tură, combină-le, de exemplu POS-3-000123 (casa 3, bonul 123).

Cashback-ul se acumulează pe ce a plătit efectiv oaspetele: amount − redeem_amount. La programele cu ștampile și vizite, bonul contează ca vizită; stamps_earned și reward_unlocked îți spun ce s-a întâmplat, ca să poată afișa casa "încă o cafea și următoarea e gratuită".

5. Plata cu cashback

Cere mai întâi soldul, lasă oaspetele să aleagă, apoi trimite bonul cu redeem_amount. Dacă între timp soldul s-a schimbat (oaspetele a plătit în altă parte cu o secundă înainte), primești 402 insufficient_balance cu suma actuală în available; afișeaz-o și lasă casierul să reîncerce.

curl
curl https://loyaltyfy.io/api/v1/transactions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CHK-2026-000124",
    "member": "2000111703779",
    "amount": 12000,
    "redeem_amount": 1250
  }'
Răspuns · 402
{
  "error": {
    "type": "invalid_request_error",
    "code": "insufficient_balance",
    "message": "redeem_amount is more than the available balance.",
    "param": "redeem_amount",
    "available": 1250
  },
  "request_id": "req_6f4c30b8ca7d4423a9d4"
}
Acordă reducerea din cashback doar după ce am răspuns cu 201 sau 200. Trimite bonul când totalul este final, dar înainte de a încasa plata. Dacă nu vine niciun răspuns, repetă cu același external_id sau închide bonul fără cashback. Altfel oaspetele poate primi reducerea și păstra soldul.

6. Acordă recompensa pentru ștampile

Când stamps.rewards_available este peste zero, oaspetele are un produs gratuit. Oferă-l la casă și înregistrează-l:

curl
curl -X POST https://loyaltyfy.io/api/v1/members/4285324286429/rewards/redeem \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: reward-4285324286429-0001"

7. Retururi și greșeli

Anulează bonul întreg după external_id. Cashback-ul sau ștampilele acumulate se retrag, iar cashback-ul folosit revine oaspetelui. Retur parțial: anulează bonul și trimite unul nou, cu suma corectată și un external_id nou.

curl
curl -X POST https://loyaltyfy.io/api/v1/transactions/CHK-2026-000123/void \
  -H "Authorization: Bearer sk_test_..."

8. Când casa este offline

Pune bonurile într-o coadă locală, cu external_id și closed_at, și trimite-le când revine conexiunea. Duplicatele sunt ignorate, așa că poți retrimite toată coada în siguranță. Căutarea oaspetelui are nevoie de rețea; dacă rețeaua lipsește, lasă casierul să sară peste fidelitate pentru acel bon, nu bloca vânzarea.

Bonuri trimise de pe serverul casei

Unele sisteme POS nu pot apela un API de la tejghea, dar pot trimite bonurile închise de pe serverul lor. Și așa funcționează: trimite același POST /v1/transactions de pe server, cu numărul cardului scanat de casier, imediat ce bonul se închide. Abonează-te la transaction.completed dacă casa vrea să afișeze rezultatul mai târziu.

Întrebări despre integrare: api@loyaltyfy.io. Răspundem în cel mult o zi lucrătoare.