Aller au contenu

Flux caisse (API native)

L’API native du moteur est servie par chaque moteur de workspace sur son port public (en local : http://localhost:47500 pour le workspace demo, slot 0). Elle est authentifiée par clé d’API (X-Api-Key, voir Authentification). Le ticket suit le schéma ticket.schema.json ; la réponse suit evaluation-result.schema.json.

Caisse Moteur du workspace
│ 1. (option) carte scannée │
├── POST /v1/customers/lookup ─────────────▶│ soldes + offres disponibles
│ 2. fin de saisie du ticket │
├── POST /v1/transactions/evaluate ────────▶│ ticket en attente (pending), coupons réservés
│◀──────────── remises par ligne, points, coupons, messages
│ 3. paiement encaissé │
├── POST /v1/transactions/{id}/commit ─────▶│ points postés, coupons utilisés/émis
│ (ou) POST /v1/transactions/{id}/cancel ──▶│ libération

Une caisse qui ne peut pas faire deux appels utilise POST /v1/transactions/commit (évaluation et validation en une fois).

{
"header": {
"ticketNumber": "000123", "storeCode": "S042", "posId": "03",
"businessDate": "2026-11-09", "timestamp": "2026-11-09T10:42:13+01:00",
"channel": "pos", "currency": "EUR",
"totals": { "grossCents": 5490, "itemCount": 3 }
},
"customer": { "identifiers": [{ "type": "card", "value": "2600000012345" }] },
"lines": [
{ "lineNo": 1, "ean": "3017620422003", "ref": "NUT750", "univers": "ALIM", "qty": 2, "unitPriceCents": 495, "lineTotalCents": 990 },
{ "lineNo": 2, "ean": "4006381333931", "ref": "STB001", "univers": "MAISON", "qty": 1, "unitPriceCents": 4500, "lineTotalCents": 4500 }
],
"payments": []
}

Règles :

  • montants en centimes ; lineTotalCents = quantité × prix unitaire − remise caisse, calculé par la caisse ;
  • totals.grossCents est le total avant offres Remizo ; le moteur ne le recalcule pas (avertissement si incohérent) ;
  • lineNo est unique dans le ticket : c’est lui qui adresse les remises dans la réponse ;
  • les drapeaux de ligne no_promo, no_points, gift_card, deposit, fee, service excluent la ligne des remises ou des points ;
  • la clé de ticket est storeCode:posId:businessDate:ticketNumber (S042:03:2026-11-09:000123) : c’est l’identité métier du ticket.

POST /v1/transactions/simulate évalue sans rien écrire (limites lues mais pas consommées, coupons vérifiés mais pas réservés). Utile pour afficher les avantages au client avant la fin du ticket.

Fenêtre de terminal
curl -s http://localhost:47500/v1/transactions/simulate \
-H "X-Api-Key: $RMZ_KEY" -H 'Content-Type: application/json' \
-d "$(jq .ticket docs-site/src/content/docs/fr/integration/examples/ticket-simple.json)"

POST /v1/transactions/evaluate enregistre un ticket pending (durée de vie 24 h par défaut) et renvoie transactionId. Les coupons présentés sont réservés, les budgets d’offres sont pré-réservés. Rien n’est encore posté dans le registre des points.

Extrait de réponse :

{
"mode": "evaluate", "status": "ok", "transactionId": "6f1c2a9e-3b7d-4e0a-9c1f-2d3e4f5a6b7c",
"ticketKey": "S042:03:2026-11-09:000123", "transactionState": "pending",
"totals": { "grossCents": 5490, "discountCents": 549, "netCents": 4941 },
"lines": [ { "lineNo": 1, "discountCents": 99 }, { "lineNo": 2, "discountCents": 450 } ],
"applied": [ { "offerCode": "WELCOME10", "valueCents": 549, "level": 0, "mode": "cumulative" } ],
"pointsMovements": [ { "counterTypeCode": "POINTS", "kind": "earn", "amount": 49, "state": "projected" } ],
"messages": [], "warnings": []
}

La caisse applique les remises ligne par ligne (lines[].discountCents) ; la part non attribuable à une ligne est donnée par totals.ticketDiscountCents. Les lignes à ajouter (produit offert) sont dans addedLines.

POST /v1/transactions/{transactionId}/commit, avec si besoin les paiements définitifs et la réservation de points à confirmer :

{ "payments": [ { "method": "card", "amountCents": 4941 } ], "pointsReservationId": null }

En une seule transaction de base de données : points crédités, coupons consommés ou émis, budgets d’offres consommés, statuts et étiquettes client appliqués, événements publiés vers le hub. La réponse est le même résultat, avec les identifiants réels (movementId, couponId, lien de rendu du coupon). Une validation répétée renvoie le même résultat (Idempotent-Replay: true).

POST /v1/transactions/{transactionId}/cancel :

  • sur un ticket en attente : libère coupons et budgets ;
  • sur un ticket validé depuis moins de transactions.cancelWindowSeconds (24 h par défaut) et sans remboursement : annule tous les effets par des mouvements de compensation. Au-delà, utilisez le remboursement.

À la fermeture de caisse ou après un incident : POST /v1/transactions/cancel-pending avec storeCode (et posId) annule tous les tickets restés en attente. Les tickets oubliés expirent aussi automatiquement (tâche transactions.expire_pending).

POST /v1/transactions/{transactionId}/refund crée une transaction fille de type refund :

{ "refundTicketNumber": "000187", "lines": [ { "lineNo": 1, "qty": 1 } ], "clawbackPoints": true, "cancelIssuedCoupons": true }
  • sans lines, le remboursement est total ; plusieurs remboursements partiels sont possibles jusqu’à épuisement des quantités ;
  • les remises sont recalculées sur les unités restantes : la réponse indique, par ligne, la remise à restituer ;
  • les points sont repris au prorata (sans solde négatif si le compteur l’interdit, avec un avertissement) ;
  • les coupons émis par le ticket d’origine et encore disponibles sont annulés.

GET /v1/transactions/{id} ou GET /v1/transactions/by-key/{ticketKey} renvoient le ticket, le résultat, l’état et les remboursements.

Objectif : p99 < 30 ms par appel (moins de 10 ms de calcul). Gardez des connexions HTTP persistantes, envoyez un X-Request-Id pour corréler vos journaux, et prévoyez un délai d’attente côté caisse de l’ordre de 2 s avec repli « vente sans avantage » : le ticket pourra être régularisé plus tard.