Före första köpet
En API-nyckel med kassans scopes
Skapa en nyckel per butik eller kassainstallation i portalen och skicka den som
Authorization: Bearer cust_live_…. En cust_test_…-nyckel
fungerar mot en helt separat testmiljö där inga meddelanden skickas på riktigt, så
bygg och certifiera mot den.
| Scope | Används till |
|---|---|
members:read |
Uppslag, medlemspanel, poäng, stämpelkort, Wallet-länkar |
members:write |
Registrering, nya identifierare, registrering av samtycke |
carts:evaluate |
Prissättning |
redemptions:read |
Hitta ett köp vid retur, status för digitalt kvitto |
redemptions:write |
Reservera, slutföra, släppa, returnera, återföra; tjäna och lösa in poäng; skicka ett Kivra-kvitto |
giftcards:read |
Saldo på presentkort |
giftcards:write |
Utfärda och fylla på kort; fristående inlösen och återbetalning |
settings:read |
Identitetsregler och samtyckestexter, läses vid start |
messaging:write (valfri) |
Mejl för dubbelt samtycke (double opt-in), mejla ett presentkort som sålts |
Konventioner som kassan måste följa
-
Belopp anges som heltal i minsta enhet.
19900är 199,00 kr. Procent anges i baspunkter:2500är 25 %. Skicka eller räkna aldrig med decimaler. -
Priser är inklusive moms om inte handlaren har ställt om till netto
(
GET /v1/tenant). - Skriv ut den momsfördelning som Custas returnerar. Räkna inte om netto och moms per rad: två oberoende avrundningar kommer förr eller senare att skilja sig åt, och skillnaden hamnar i en momsdeklaration.
-
Idempotency-Key(8 till 255 tecken, en per logisk operation) krävs vid reservation, slutförande, släpp, återföring och returer, och när presentkort utfärdas eller mejlas. Ett nytt försök med samma nyckel och body spelar upp det första svaret igen. Skapa nyckeln när kassören trycker på knappen, och behåll den tills du får ett svar. -
Fel är RFC 9457 problem+json. Förgrena på
code, aldrig påtitle.
Läs in handlarens konfiguration vid start
GET /v1/tenant/identity-rules
GET /v1/consent-wordings
Identitetsreglerna anger vilka identifierare handlaren accepterar (email,
phone, personnummer, member_no,
card_no, pan_alias, …), vilka som är unika och vilka som
krävs vid registrering. Bygg registreringsformuläret utifrån dem: en typ utan regel
nekas.
Samtyckestexterna är de exakta texter en kund får visas när hen tillfrågas om samtycke
till marknadsföring. Cacha den aktiva texten per kanal, ändamål och språk. Du behöver
dess id när du registrerar samtycket.
1. Identifiera kunden
Kunden skannar ett medlemskort, ett Wallet-kort eller en QR-kod i appen, eller ger kassören sitt telefonnummer eller personnummer.
POST /v1/members/lookup
{ "keyType": "phone", "value": "070-123 45 67" }
# → 200
{
"id": "0199a4f0-1c2d-7e3f-8a4b-5c6d7e8f9a0b",
"status": "active",
"displayName": "Anna Svensson",
"identities": [
{ "keyType": "phone", "displayHint": "+4670*****67", "isPrimary": true },
{ "keyType": "personnummer", "displayHint": "19811218-****", "isPrimary": false }
],
"tags": [{ "name": "VIP", "color": "gold" }],
"pointsBalance": 1240,
"orderCount": 17,
"lifetimeSpendMinor": 1245000
}
-
Uppslaget är en
POST, så att ett personnummer aldrig syns i en URL eller en åtkomstlogg. Alla skrivsätt matchar:811218-9876och198112189876är samma person, och telefonnummer normaliseras. -
404: inte medlem. Erbjud registrering. -
429på personnummer: för många missar från den här nyckeln den senaste timmen. Använd en annan identifierare i stället. -
status:active: en vanlig medlem.-
blocked: prissätts som gäst och varken tjänar eller använder något. Behandla medlemmen som anonym. -
restricted: fortfarande medlem i kassan, men får inga meddelanden eller erbjudanden.
-
Identifierare returneras bara som maskerade
displayHint. Visa dem; be aldrig om hela värdet.
Spara id under resten av transaktionen. Du kan också hoppa över uppslaget
och skicka den skannade identifieraren direkt i varukorgen, men om du slår upp först
kan kassören hälsa på kunden och se saldot.
Medlemspanelen
GET /v1/members/{id}/points # saldo, vad det är värt nu, nivå, poäng som snart förfaller
GET /v1/members/{id}/stamp-cards # stämplar per kort och oanvända belöningskoder
Stämpelkortsbelöningar med status: "active" är koder som medlemmen har
tjänat in. Visa dem så att kassören kan erbjuda
”du har en gratis kaffe, vill du använda den nu?”. För att använda en lägger
du till dess code i varukorgen.
Personliga erbjudandekoder tillämpas inte automatiskt. Medlemmen
visar dem i medlemsappen, på sitt Wallet-kort eller i ett mejl, där var och en har en
QR-kod och en Code 128-streckkod. Skanna in dem i varukorgens codes.
2. Registrera en ny medlem i kassan
Skapa medlemmen
POST /v1/members
{
"displayName": "Anna Svensson",
"locale": "sv-SE",
"country": "SE",
"identities": [
{ "keyType": "phone", "value": "0701234567", "isPrimary": true },
{ "keyType": "email", "value": "Anna.Svensson@example.com" }
],
"attributes": { "signupStore": "uppsala-01", "signupTill": "4" }
}
- Skicka minst en identifierare, och alla identifierare som identitetsreglerna anger som obligatoriska.
-
Ett personnummer sätter också
birthDate, som födelsedagskampanjer använder. Annars kan du skickabirthDatesjälv. -
409 identity_conflict: en unik identifierare tillhör redan någon. Svaret säger medvetet inte vem. Slå upp kunden med samma identifierare och fortsätt med den medlemmen; skapa inte en till. -
422: en identifierartyp som handlaren inte accepterar, eller ett felaktigt värde.errors[].fieldpekar ut det.
Välkomstautomationer som handlaren har satt upp (ett välkomstmejl, en välkomstkod) körs av sig själva. Kassan behöver inte göra något mer.
Registrera samtycke till marknadsföring, bara om kunden sa ja
Medlemskap och samtycke till marknadsföring är skilda saker: att gå med i programmet får inte innebära samtycke. Fråga, och registrera bara ett uttryckligt ja.
PUT /v1/members/{id}/consents/email/marketing
{
"state": "granted",
"source": "pos",
"sourceDetail": "till 4, Uppsala",
"wordingId": "0199a4f1-…", # samtyckestexten som visades på kunddisplayen
"actor": "staff:1043" # kassören
}
-
Kanalen är
email,sms,push,postellerphone; ändamålet ärmarketingellerprofiling. Varje par är en egen post. -
Skicka alltid
wordingId. Ett samtycke som inte kan visa vad personen fick veta är ett svagt bevis. -
För det starkaste beviset skickar du i stället ett dubbelt samtycke (double opt-in),
och kunden bekräftar via en länk:
POST /v1/members/{id}/consent-confirmationsmedchannel,purpose,templateKeyochwordingId. -
Ett återkallande (
"state": "withdrawn") nekas aldrig. Erbjud det i kassan också.
Ge kunden sitt kort
GET /v1/members/{id}/wallet/google # → { "saveUrl": "https://pay.google.com/…" }
GET /v1/members/{id}/wallet/apple # → .pkpass-fil
Visa Googles saveUrl som en QR-kod på kunddisplayen. Kortet uppdaterar
sig självt när poäng, nivå och namn ändras, och det är dess streckkod du skannar nästa
gång.
3. Prissätt varukorgen
Anropa POST /v1/carts/evaluate
varje gång kundkorgen ändras: en rad läggs till eller tas bort, ett
antal ändras, en medlem identifieras eller en kod skannas. Anropet läser bara och
reserverar ingenting, så det är ofarligt att anropa ofta.
POST /v1/carts/evaluate?explain=true
{
"currency": "SEK",
"channel": "pos",
"country": "SE",
"storeId": "uppsala-01",
"member": { "memberId": "0199a4f0-1c2d-7e3f-8a4b-5c6d7e8f9a0b" },
"codes": ["WELCOME50"],
"lines": [
{
"id": "L1", "sku": "TSH-SPORT-2PK", "quantity": 2,
"unitPriceMinor": 19900, "taxRateBp": 2500,
"categories": ["clothing"], "brands": ["Acme"],
"description": "Sport T-shirt (2-pack)"
},
{
"id": "L2", "sku": "COF-LATTE", "quantity": 1,
"unitPriceMinor": 4500, "taxRateBp": 1200,
"categories": ["cafe"],
"description": "Caffe latte"
}
]
}
Identifiera medlemmen
Använd { "memberId": … } efter ett uppslag, eller skicka den skannade
identifieraren direkt som { "keyType": "card_no", "value": "…" }. För att
känna igen en medlem på kortet hen betalar med skickar du betalleverantörens kortalias
(Adyens alias) som
{ "keyType": "pan_alias", "value": "M469509594859802" } — aldrig själva
kortnumret, som nekas. Kampanjer för bara medlemmar, för nivåer och för födelsedagar
gäller bara när en medlem är identifierad.
Regler för rader
-
idmåste vara stabilt så länge kundkorgen finns. Det är fördelningsnyckeln: om det ändras mellan anropen kan en rabatt flytta mellan rader. -
Skicka allt som kampanjer kan rikta sig mot:
categories,collections,brands,tags,isSale,productId,variantId. En kampanj på ”kläder” kan inte matcha en rad som inte säger att den är kläder. -
excludeFromDiscounts: truegäller tobak, pant och allt annat som lagen eller butiken håller utanför rabatter. -
isGiftCard: truemarkerar ett presentkort som säljs. Det rabatteras aldrig. descriptionkrävs om handlaren skickar digitala kvitton.-
Varor som säljs per vikt:
quantity: 1,unitPriceMinorär priset för den vägda mängden, ochmeasure: { "value": 0.532, "unit": "kg" }.
Offerten
{
"quoteHash": "7f3a9c…e21b", # spara den: du reserverar mot den
"memberId": "0199a4f0-1c2d-7e3f-8a4b-5c6d7e8f9a0b",
"subtotalMinor": 44300,
"totalDiscountMinor": 8980,
"totalMinor": 35320,
"amountDueMinor": 35320,
"lines": [
{ "lineId": "L1", "baseMinor": 39800, "discountMinor": 8422, "totalMinor": 31378,
"netMinor": 25102, "vatMinor": 6276,
"allocations": [
{ "promotionId": "…", "amountMinor": 3980, "netMinor": 3184, "vatMinor": 796 },
{ "promotionId": "…", "codeId": "…", "amountMinor": 4442, "netMinor": 3554, "vatMinor": 888 }
] },
{ "lineId": "L2", "baseMinor": 4500, "discountMinor": 558, "totalMinor": 3942,
"netMinor": 3520, "vatMinor": 422, "allocations": [ … ] }
],
"appliedPromotions": [
{ "reference": "Members: 10% off clothing", "klass": "product", "amountMinor": 3980 },
{ "reference": "Welcome 50 kr", "codeId": "…", "klass": "order", "amountMinor": 5000 }
],
"rejectedCodes": [],
"vatBreakdown": [
{ "taxRateBp": 2500, "netMinor": 25102, "vatMinor": 6276, "grossMinor": 31378 },
{ "taxRateBp": 1200, "netMinor": 3520, "vatMinor": 422, "grossMinor": 3942 }
]
}
| Fält | Kassan |
|---|---|
lines[].totalMinor, discountMinor |
Visar varje rads pris efter rabatt, på skärmen och kvittot |
appliedPromotions[] |
Skriver ut en rad per erbjudande: ”Welcome 50 kr −50,00” |
rejectedCodes[] |
Berättar för kassören varför en kod inte gällde. Resten av varukorgen prissätts ändå. |
vatBreakdown |
Skriver ut den ordagrant som kvittots momssammanställning |
amountDueMinor |
Debiterar det som den del som betalas med pengar |
quoteHash |
Sparar det till reservationen |
?explain=true lägger till ett spår för varje kampanj: tillämpad, hoppad
över med en anledning, eller inte prövad (till exempel needs_code eller
wrong_channel). Använd det för att svara på
”varför gällde inte mitt erbjudande?” och för att driva
merförsäljningsförslag. Utelämna det i den prestandakritiska vägen om du inte använder
det.
Koder kan skannas eller skrivas in. Versaler, mellanslag, bindestreck och de
förväxlingsbara I/L/O normaliseras, så welcome-50 och
WELCOME 5O matchar båda WELCOME50.
4. Presentkort och poäng som betalning
Presentkort och poäng är betalmedel, inte rabatter. De dras efter
alla rabatter och minskar bara amountDueMinor, aldrig en rad eller dess
moms. Lägg dem i samma varukorg:
POST /v1/carts/evaluate
{
…the same cart…,
"tenders": [
{ "type": "gift_card", "code": "GC-7Q4M-XK2P-9TND" },
{ "type": "points", "points": 500 }
]
}
# → i offerten
"amountDueMinor": 20320,
"tenders": [
{ "position": 0, "type": "gift_card", "status": "applied", "lastFour": "9TND",
"amountMinor": 10000, "remainingMinor": 0 },
{ "position": 1, "type": "points", "status": "applied", "points": 500,
"amountMinor": 5000, "remainingPoints": 740 }
]
-
Betalmedel dras i den ordning de skickas, upp till fem. Utelämna
amountMinorellerpointsför att använda så mycket som återstår att betala. -
Ett nekat betalmedel betalar ingenting och anger varför i
reason:not_found,expired,no_balance,no_member,below_stepmed flera. Köpet kan ändå genomföras. -
Skriv ut
remainingMinorochremainingPointspå kvittot. -
För att kontrollera ett kort innan det används som betalning anropar du
POST /v1/gift-cards/lookupmed{ "code": "…" }. Det som går att använda just nu ärbalanceMinorminusheldMinor. -
Vad medlemmens poäng är värda:
redeemablePointsochredeemableMinoriGET /v1/members/{id}/points.
Presentkortskoder fungerar som kontanter: den som har koden kan använda den. Logga dem aldrig, och skriv aldrig ut dem i sin helhet.
5. Betalning: reservera, betala, slutför
Reservera när kassören trycker på ”Betala”
POST /v1/redemptions
Idempotency-Key: till4-20261020-000731-reserve
{
"cart": { …exactly the cart you last evaluated, tenders included… },
"quoteHash": "7f3a9c…e21b",
"externalRef": "UPS01-T4-000731",
"ttlSeconds": 600,
"staff": { "id": "1043", "name": "Erik" }
}
-
Custas prissätter varukorgen på nytt på servern och jämför med
quoteHash. Din offert används aldrig som indata. -
Vid
201(status: "held") läggs varje kodanvändning, kampanjbudget, presentkortsbelopp och poäng i offerten undan, och ingen annan kassa kan ta dem. Debitera reservationensamountDueMinor. -
externalRefär ditt kvitto- eller transaktionsnummer. Det är unikt per handlare, och det är så du hittar köpet igen vid en retur. -
ttlSecondsär 30 till 3 600, 900 som standard. Om kassan kraschar löper reservationen ut och allt återställs av sig självt.
En och samma väg tillbaka för varje 409
quote_stale, code_exhausted, budget_exhausted,
member_limit_reached och tender_unavailable betyder alla
att varukorgen inte längre prissätts som i offerten. Alla innehåller den
aktuella offerten:
{
"type": "https://app.custas.se/docs/problems/code-exhausted",
"status": 409,
"code": "code_exhausted",
"title": "Code has no redemptions left",
"quote": { "quoteHash": "9b21d4…07ac", "totalMinor": 40320, "rejectedCodes": [ … ], … }
}
Visa den nya totalsumman, låt kunden godkänna den och reservera igen mot
quote.quoteHash med en ny Idempotency-Key. Förgrena inte
på vilken av de fem koderna du fick: när två kassor tävlar om den sista användningen
av en kod är det tajmingen som avgör vilken förloraren ser, inte betydelsen.
Ta betalt, eller släpp reservationen
Debitera amountDueMinor med kort, kontanter, Swish och så vidare. Om
betalningen misslyckas eller kunden går därifrån släpper du reservationen direkt i
stället för att vänta på att den löper ut. Då blir den sista användningen av en kod
ledig för nästa kund i kön.
POST /v1/redemptions/{id}/release
Idempotency-Key: till4-20261020-000731-release
{ "reason": "cancelled" }
Slutför (commit) när betalningen har gått igenom
POST /v1/redemptions/{id}/commit
Idempotency-Key: till4-20261020-000731-commit
{
"receipt": {
"number": 731,
"purchasedAt": "2026-10-20T12:04:11+02:00",
"store": {
"name": "Acme Uppsala",
"organizationNumber": "556677-8899",
"unitId": "uppsala-01",
"address": { "streetAddress": "Kungsgatan 1", "postalCode": "753 21",
"locality": "Uppsala", "country": "SE" }
},
"register": { "id": "T4", "serialNumber": "CR-0042-1187" },
"controlUnit": { "id": "KE-118833", "code": "A1B2C3D4E5" },
"cashier": { "id": "1043", "name": "Erik" },
"roundingMinor": -20
},
"payments": [
{ "method": "card", "amountMinor": 20300,
"card": { "maskedPan": "535583******7361", "brand": "Mastercard",
"approvalCode": "123456", "terminalId": "TID0042",
"token": { "scheme": "par", "value": "5001…" } } }
]
}
-
paymentsmåste summera tillamountDueMinorplusreceipt.roundingMinor(öresavrundning), annars får du422och ingenting slutförs. Presentkort och poäng finns redan bland reservationens betalmedel; lista dem inte igen. -
roundingMinorligger mellan-100och100: högst en hel krona (eller annan valutaenhet) åt vardera hållet. - Skicka aldrig ett fullständigt kortnummer: det nekas. Skicka maskerat PAN eller BIN, och PAR-token om din terminal ger en. Med en PAR-token kan ett digitalt kvitto hitta en kund som betalade med kort utan att identifiera sig.
-
receiptochpaymentsändrar inte priset, men det är av dem det digitala kvittot (Kivra) och medlemmens kvittohistorik byggs. Skicka dem. -
409 reservation_not_held: reservationen har löpt ut eller släppts. Prissätt och reservera igen innan du skriver ut. - Tappat svar? Gör ett nytt försök med samma Idempotency-Key. Samtidiga slutföranden av samma reservation leder till exakt ett lyckat.
Identifierad medlem, ingen rabatt
Reservera och slutför ändå. Det är så medlemmen tjänar poäng och stämplar, och så
köpet räknas in i medlemmens antal köp och totala köpbelopp. Bara ett anonymt köp utan
inlösen nekas (409 conflict); för sådana anropar du inte Custas.
6. Vad som händer efter slutförandet
Kassan gör ingenting för något av detta. Det sker när köpet slutförs:
| Vad | Var det syns |
|---|---|
| Poäng på den del som betalas med pengar (inte på använda poäng, inte på köpta presentkort) | GET /v1/members/{id}/points, points.earned |
| Uppgraderad nivå | tier.changed |
| Stämplar läggs till, och en belöningskod utfärdas när ett kort blir fullt |
GET /v1/members/{id}/stamp-cards, stamps.rewarded
|
| Kodanvändningar och kampanjbudget förbrukas | redemption.committed |
| Digitalt kvitto till medlemmens Kivra-brevlåda, om handlaren använder Kivra och medlemmen har ett läsbart personnummer | GET /v1/redemptions/{id}/kivra |
| Kvittot i medlemsappen | Medlemmens egen kvittolista |
För att skriva ut ”Du har nu 1 740 poäng” eller ”2 stämplar kvar till en gratis
kaffe” läser du GET /v1/members/{id}/points och
GET /v1/members/{id}/stamp-cards efter slutförandet.
Om ett Kivra-kvitto misslyckades eller hoppades över köar
POST /v1/redemptions/{id}/kivra det igen. Ett kvitto som redan har
skickats nekas, så kunden får aldrig två exemplar.
7. Returer och makuleringar
Hitta det ursprungliga köpet
Skanna kvittots streckkod och slå sedan upp dess nummer:
GET /v1/redemptions?externalRef=UPS01-T4-000731
Köpets lines[] innehåller fördelningen
som den var vid köpet (rabatt, netto och moms per rad), plus
returnedQuantity hittills.
Delretur: några enheter lämnas tillbaka
POST /v1/redemptions/{id}/returns
Idempotency-Key: till2-20261102-000088-return
{
"lines": [{ "lineId": "L1", "quantity": 1 }],
"reason": "wrong size",
"receipt": { "number": 88, "purchasedAt": "2026-11-02T15:30:00+01:00",
"store": { "name": "Acme Uppsala", "unitId": "uppsala-01" },
"register": { "id": "T2" }, "cashier": { "id": "2001" } },
"payments": [{ "method": "card", "amountMinor": 15689,
"card": { "maskedPan": "535583******7361" } }]
}
-
Varje enhet återbetalas med sin andel av raden som den såldes,
rabatt inräknad, aldrig till dagens pris. Läs beloppet, med netto och moms, från den
nya posten i svarets
returns; räkna inte ut det själv. - Poängen som enheterna gav dras tillbaka, och medlemmens totala köpbelopp minskar.
-
En delretur tar inte tillbaka koden eller kampanjen, eller de presentkort och poäng
som användes för betalningen. Återbetala det värdet via
payments, eller sätt tillbaka det på kortet medPOST /v1/gift-cards/{id}/refund. -
422: fler enheter tillbaka än som såldes.409: köpet har redan återförts.
Fullständig återföring: en makulering eller retur av hela kvittot
POST /v1/redemptions/{id}/reverse
Idempotency-Key: till2-20261102-000088-reverse
{ "reason": "return, receipt 88" }
Det här tar tillbaka allt. Kodanvändningar kommer tillbaka (en förbrukad kod blir aktiv igen), kampanjbudgeten krediteras, och presentkort och poäng som användes för betalningen återbetalas till kortet och medlemmen. En andra återföring ändrar ingenting, och efter delreturer tar en återföring bara tillbaka det som återstår.
8. Sälja och fylla på presentkort
Sälj ett nytt kort
-
Lägg kortet i kundkorgen som en rad med
"isGiftCard": true, och prissätt, reservera, betala och slutför som vanligt. Ingen kampanj ger rabatt på det. -
Efter slutförandet utfärdar du kortet för det sålda beloppet. Svaret innehåller
codeen enda gång: skriv ut den eller koda in den på kortet. Ett nytt försök med samma nyckel returnerar samma kort i stället för att ladda ett till. -
Du kan också mejla det som en present med
POST /v1/gift-cards/{id}/deliveries.
POST /v1/gift-cards
Idempotency-Key: till4-20261020-000731-gc-L3
{
"amountMinor": 50000,
"memberId": "0199a4f0-…",
"note": "receipt UPS01-T4-000731",
"code": "6012345678901234" # bara för ett förtryckt kort; utelämna för att generera en kod
}
Fyll på ett befintligt kort
Sälj påfyllningen som en isGiftCard-rad, slutför och sedan:
POST /v1/gift-cards/lookup { "code": "…" } # → id
POST /v1/gift-cards/{id}/top-up { "amountMinor": 20000, "externalRef": "UPS01-T4-000732" }
externalRef gör det säkert att försöka igen: samma kvitto fyller på en
gång. Ett inaktiverat kort svarar 409, ett utgånget 410.
9. Kassor som bara kan göra ett anrop
Vissa kassor kan bara anropa en gång, efter köpet. De kan använda de här fristående endpoints, som var och en är säker att anropa igen med samma kvittonummer. De reserverar ingenting, så en annan kassa kan använda samma saldo i samma ögonblick: välj reservera och slutför där du kan.
POST /v1/members/{id}/points/earn { "amountMinor": 35320, "externalRef": "UPS01-T4-000731" }
POST /v1/members/{id}/points/redeem { "points": 500, "externalRef": "UPS01-T4-000731" }
POST /v1/gift-cards/redeem { "code": "…", "amountMinor": 10000,
"externalRef": "UPS01-T4-000731", "allowPartial": true }
POST /v1/gift-cards/{id}/refund { "amountMinor": 10000, "externalRef": "UPS01-T4-R88" }
Kvittonummer delas med reservationer: ett kvitto som redan har slutförts via reservera
och slutför har fått sina poäng, och skickas det till points/earn
returneras den intjäningen i stället för att poängen tjänas två gånger.
Kampanjer och koder kan inte tillämpas på det här sättet. De kräver prissättning, reservation och slutförande.
10. Backoffice: webhooks
Kassan själv behöver inga webhooks. Ett backoffice för kassasystemet vill oftast spegla det som händer i Custas:
POST /v1/webhook-endpoints
{
"url": "https://pos.example.com/hooks/custas",
"eventTypes": ["redemption.committed", "redemption.reversed", "redemption.returned",
"member.created", "member.updated", "member.merged", "member.deleted",
"points.earned", "tier.changed", "gift_card.issued", "gift_card.redeemed"]
}
-
Signeringshemligheten returneras en gång. Verifiera varje leverans:
custas-signature: v1=<hex>är HMAC-SHA256 över<custas-timestamp>.<raw body>, beräknad på de bytes du tog emot. -
Leverans sker minst en gång, så ta bort dubbletter med
custas-event-id. -
member.mergedochmember.deletedspelar roll om du cachar medlems-id:n. Ett sammanslaget id svarar404medmergedInto, det id som ska användas i stället. En raderad medlem måste försvinna ur din cache också.
11. Fel och offline
| Svar | Var | Kassan |
|---|---|---|
404 not_found |
Uppslag | Erbjuder registrering |
404 med mergedInto |
Hämta medlem | Använder det nya id:t |
409 identity_conflict |
Skapa medlem | Slår upp med samma identifierare och använder den medlemmen |
409 med en quote |
Reservera |
Visar den nya totalsumman, reserverar igen på quote.quoteHash
|
409 reservation_not_held |
Slutför | Prissätter och reserverar igen |
409 idempotency_request_in_progress |
Alla anrop med nyckel | Väntar retryAfter, försöker igen med samma nyckel |
422 idempotency_key_reused |
Alla anrop med nyckel | En bugg: en ny operation återanvände en gammal nyckel |
422 validation_failed |
Alla | errors[].field anger vilket fält som ska rättas |
429 rate_limited |
Alla | Väntar under Retry-After |
402 feature_not_in_plan |
Alla | Döljer funktionen för den här handlaren |
401, 403 insufficient_scope |
Alla | Flaggar ett konfigurationsproblem, inte ett kassörsproblem |
5xx, timeout |
Alla | Försöker igen med samma nyckel för anrop med nyckel |
Om Custas inte går att nå säljer du till hyllpris och sparar
medlemmens identifierare och kvittonumret. När anslutningen är tillbaka skickar du
kvittot till POST /v1/members/{id}/points/earn så att medlemmen ändå får
sina poäng. Koder och presentkort kan inte godtas offline, eftersom ingenting kan
reservera dem.
12. Checklista för integrationen
En kassa som gör allt detta använder allt som Custas erbjuder vid disken.
- Identitetsregler och samtyckestexter läses in vid start
- Medlemsuppslag via skanning (kort, Wallet-kort, QR-kod i appen) och via inskriven identifierare
- Registrering med handlarens obligatoriska identifierare; en konflikt leder till uppslag
- Samtycke till marknadsföring registreras med text, källa och kassör; återkallande är möjligt
- Wallet-kort erbjuds vid registrering
- Varukorgen prissätts om vid varje ändring, med stabila rad-id:n och produktattribut
- Koder skannas eller skrivs in; nekade koder visas med anledning
- Stämpelkortsbelöningar erbjuds när medlemmen har en
- Saldokontroll för presentkort och presentkort som betalmedel
- Poäng visas och erbjuds som betalning
- Reservation vid ”Betala”, med en enda 409-väg som visar den nya totalsumman
- Reservationen släpps vid misslyckad eller avbruten betalning
- Slutförande med kvitto, betalningar (maskerat kort och PAR), personal och öresavrundning
- Identifierade köp utan erbjudanden reserveras och slutförs ändå
- Moms och rabattrader per rad skrivs ut från offerten, inte omräknade
- Uppdaterade poäng och stämplar skrivs ut på kvittot
- Delretur via kvittonummer; fullständig återföring vid makulering
- Presentkort säljs och fylls på
- Varje anrop som flyttar pengar görs om med samma nyckel
- Offline-reserv som ger poäng i efterhand
- Certifierad mot en
cust_test_-nyckel före driftstart