API-referentie

Wallet-endpoints

Endpoints om wallet-saldo's op te vragen, het puntengrootboek te doorlopen en het saldo handmatig aan te passen.


Wallet-saldo ophalen

GET /api/v1/customers/{customer}/wallet

Geeft het huidige wallet-saldo van een klant terug, met daarbij de punten die binnenkort verlopen.

Padparameters

ParameterTypeBeschrijving
customerstringHet klant-ID

Response

{
    "data": {
        "id": 1,
        "customer_id": 1,
        "balance": 1500,
        "expiring_soon": 200,
        "created_at": "2024-03-15T12:30:00Z",
        "updated_at": "string"
    }
}
VeldBeschrijving
balanceHet huidige totale puntensaldo
expiring_soonPunten die binnenkort verlopen (als het verlopen van punten aanstaat)

Grootboekregels opvragen

GET /api/v1/customers/{customer}/ledger

Geeft alle puntbewegingen in de wallet van een klant terug, gepagineerd en met de nieuwste eerst.

Padparameters

ParameterTypeBeschrijving
customerstringHet klant-ID

Queryparameters

ParameterTypeVerplichtBeschrijving
date_fromstringNeeAlleen regels die op of na dit moment zijn aangemaakt. De vergelijking loopt over created_at, dus een datum zonder tijd begint om middernacht UTC
date_tostringNeeAlleen regels die op of vóór dit moment zijn aangemaakt. Een datum zonder tijd stopt dus om middernacht en laat die dag zelf buiten beschouwing
typestringNeeEén van earn, adjust, redeem of expire

Pagineren doe je met ?page=. De paginator leest die zelf uit de querystring, dus hij staat niet in de parametertabel hierboven.

Response

{
    "data": {
        "data": [
            {
                "id": 1,
                "wallet_id": 1,
                "type": "earn",
                "amount": 250,
                "running_balance": 1500,
                "reason": "Points earned from transaction #42",
                "causer_type": "user",
                "causer_id": "0",
                "created_at": "2024-03-15T12:30:00Z"
            }
        ],
        "links": {
            "first": "string",
            "last": "string",
            "prev": "string",
            "next": "string"
        },
        "meta": {
            "current_page": 1,
            "last_page": 2,
            "per_page": 15,
            "total": 20
        }
    }
}

Typen grootboekregels

TypeTeken van het bedragBeschrijving
earnPositiefPunten verdiend met een transactie
adjustPositief of negatiefHandmatige saldo-aanpassing
redeemNegatiefPunten besteed aan een beloning
expireNegatiefPunten die verlopen zijn

Wallet-saldo aanpassen

POST /api/v1/customers/{customer}/wallet/adjust

Boekt handmatig punten bij of af op de wallet van een klant, en legt dat vast als een nieuwe grootboekregel van het type adjust.

Padparameters

ParameterTypeBeschrijving
customerstringHet klant-ID

Request body

VeldTypeVerplichtBeschrijving
amountintegerJaPunten die je bijboekt (positief) of afboekt (negatief). Een geheel getal, niet 0, dat in een signed 32-bit integer past.
reasonstringJaToelichting bij de aanpassing (max. 500 tekens)
idempotency_keystringJaUnieke sleutel die dubbele aanpassingen voorkomt (max. 255 tekens)

Example request

{
    "amount": 250,
    "reason": "Points earned from transaction #42",
    "idempotency_key": "goodwill-2024-0042"
}
curl -X POST https://puntjes.app/api/v1/customers/1/wallet/adjust \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount":250,"reason":"Points earned from transaction #42","idempotency_key":"goodwill-2024-0042"}'

De aangemaakte grootboekregel, in dezelfde vorm als de regels die GET /customers/{customer}/ledger teruggeeft. running_balance is het wallet-saldo ná de aanpassing.

Response

{
    "data": {
        "id": 1,
        "wallet_id": 1,
        "type": "earn",
        "amount": 250,
        "running_balance": 1500,
        "reason": "Points earned from transaction #42",
        "causer_type": "user",
        "causer_id": "0",
        "created_at": "2024-03-15T12:30:00Z"
    }
}

Idempotentie

De idempotency_key moet uniek zijn per wallet. Pas je aan met een idempotency_key die al bestaat voor deze klant en is het amount hetzelfde, dan:

  • wordt de oorspronkelijke grootboekregel teruggegeven
  • wordt het saldo niet een tweede keer verplaatst
  • wordt er geen nieuwe grootboekregel aangemaakt

Daardoor kun je deze aanroep veilig opnieuw sturen na een netwerkfout. Een herhaling krijgt ook antwoord wanneer het saldo de aanpassing niet meer kan dragen: de oorspronkelijke aanpassing is al verwerkt, dus een retry faalt nooit met INSUFFICIENT_BALANCE.

Komt dezelfde sleutel terug met een ander amount, dan is dat een andere aanpassing en geen retry. Die wordt geweigerd met IDEMPOTENCY_KEY_CONFLICT en er wordt niets weggeschreven. Geef elke aanpassing dus een eigen sleutel. Het reason wordt niet vergeleken: een kassa mag dat tussen twee pogingen anders formuleren zonder er iets anders mee te bedoelen.

Negatieve aanpassingen

Controleer eerst of de klant genoeg saldo heeft voor je punten afboekt. Een negatieve aanpassing die het saldo onder nul zou brengen, wordt geweigerd.

Fouten

CodeStatusBeschrijving
CUSTOMER_NOT_FOUND404Geen klant gevonden met het opgegeven ID
IDEMPOTENCY_KEY_CONFLICT422Deze idempotency_key is al gebruikt, voor een ander bedrag
INSUFFICIENT_BALANCE422De aanpassing zou het saldo onder nul brengen
WALLET_NOT_FOUND404De klant heeft geen wallet

Een wallet-pas downloaden

GET /api/v1/customers/{customer}/wallet-pass

Bouwt de loyaltypas van de klant voor Apple Wallet of Google Wallet. Puntjes leest het saldo op het moment dat je dit endpoint aanroept, dus een pas draagt nooit een verouderd cijfer.

De twee platformen antwoorden verschillend. apple geeft het ondertekende .pkpass-bestand zelf terug, als bijlage. Geef die bytes rechtstreeks door aan de browser of app van de klant. google geeft JSON terug met een link waarmee de klant de pas bewaart. Bewust geen redirect: de aanroeper is meestal een kassa of een webshop-backend die de URL nodig heeft om er een knop van te maken.

Padparameters

ParameterTypeBeschrijving
customerstringHet klant-ID

Queryparameters

ParameterTypeVerplichtBeschrijving
platformstringJaVoor welke wallet je bouwt: apple of google

Example request

curl https://puntjes.app/api/v1/customers/1/wallet-pass?platform=google \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response

{
    "data": {
        "save_url": "https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9"
    }
}

Het voorbeeld hierboven is de google-variant. Met platform=apple krijg je application/vnd.apple.pkpass terug met een Content-Disposition: attachment-header, geen JSON.

Fouten

CodeStatusBeschrijving
CUSTOMER_NOT_FOUND404Geen klant gevonden met het opgegeven ID

De klantenkaart naar de klant sturen

POST /api/v1/customers/{customer}/send-card

Mail de klant zijn klantenkaart terwijl hij nog aan de kassa staat. Het is dezelfde mail als die van de verstuurknop in het beheerportaal: de ondertekende .pkpass als bijlage, een "Toevoegen aan Google Wallet"-knop en de QR-code van de kaart om in de winkel te scannen.

Het versturen loopt via een wachtrij, dus 202 betekent dat de mail is aangenomen om te versturen. Niets in de response zegt dat hij is aangekomen.

Padparameters

ParameterTypeBeschrijving
customerstringHet klant-ID

Request body

VeldTypeVerplichtBeschrijving
channelstringNeeWaar de kaart naartoe gaat. email is vandaag het enige kanaal waarop Puntjes kan bezorgen, en het veld weglaten betekent email.

Example request

{
    "channel": "email"
}
curl -X POST https://puntjes.app/api/v1/customers/1/send-card \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"email"}'

Response (202 Accepted)

{
    "data": {
        "customer_id": 1,
        "channel": "email",
        "queued": true
    }
}

Twee weigeringen vang je best apart op. Een klant van wie je geen e-mailadres hebt krijgt CUSTOMER_HAS_NO_EMAIL en geen algemene validatiefout, zodat de kassier ernaar kan vragen. Een tweede verzending voor dezelfde klant binnen de wachttijd krijgt CARD_SEND_THROTTLED, met details.retry_after in seconden. Dat is een kassier die twee keer drukt, niet je integratie die over haar plan heen gaat, en na 60 seconden lost het zichzelf op. Een andere channel dan email geeft VALIDATION_ERROR, en een gedeactiveerde klant geeft CUSTOMER_DEACTIVATED.

Er is hier geen idempotency_key, anders dan bij de endpoints die punten verplaatsen. De wachttijd maakt opnieuw proberen veilig: een tweede poging binnen die tijd wordt geweigerd in plaats van verstuurd. Een sleutel had trouwens niet geholpen, want tien keer drukken levert tien sleutels op.

Marketingtoestemming speelt hier geen rol. De klant vraagt aan de kassa om zijn eigen kaart, dus een afmelding voor campagnemails blokkeert dit niet.

Fouten

CodeStatusBeschrijving
CARD_SEND_THROTTLED429Deze klant kreeg zijn kaart net al toegestuurd; wacht details.retry_after seconden
CUSTOMER_DEACTIVATED422De klant is gestopt met het programma, dus er wordt geen kaart verstuurd
CUSTOMER_HAS_NO_EMAIL422De klant heeft geen e-mailadres, dus er is geen adres om de kaart naartoe te sturen
CUSTOMER_NOT_FOUND404Geen klant van jou heeft dit ID. Een gedeactiveerde klant wordt wél gevonden en geweigerd met CUSTOMER_DEACTIVATED

De klantenkaart versturen via je eigen id

POST /api/v1/customers/by-external-id/{externalId}/send-card

Dezelfde verzending als hierboven, maar je bereikt ze via de external_id die jij de klant gaf. Een kassasysteem hoeft ons numerieke id dus niet bij te houden om op deze knop te drukken. Heb je beide, kies dan de id-variant: dat scheelt één opzoeking in de index.

De wachttijd hangt aan de klant en niet aan de route, dus afwisselen tussen de twee varianten kan de mails niet verdubbelen.

Padparameters

ParameterTypeBeschrijving
externalIdstringDe external_id die je deze klant in je eigen systeem hebt gegeven

Request body

VeldTypeVerplichtBeschrijving
channelstringNeeWaar de kaart naartoe gaat. email is vandaag het enige kanaal waarop Puntjes kan bezorgen, en het veld weglaten betekent email.

Example request

{
    "channel": "email"
}
curl -X POST https://puntjes.app/api/v1/customers/by-external-id/POS-4471/send-card \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel":"email"}'

Response (202 Accepted)

{
    "data": {
        "customer_id": 1,
        "channel": "email",
        "queued": true
    }
}

Fouten

CodeStatusBeschrijving
CARD_SEND_THROTTLED429Deze klant kreeg zijn kaart net al toegestuurd; wacht details.retry_after seconden
CUSTOMER_DEACTIVATED422De klant is gestopt met het programma, dus er wordt geen kaart verstuurd
CUSTOMER_HAS_NO_EMAIL422De klant heeft geen e-mailadres, dus er is geen adres om de kaart naartoe te sturen
EXTERNAL_ID_NOT_FOUND404Geen klant van jou heeft deze external_id. Een gedeactiveerde klant wordt wél gevonden en geweigerd met CUSTOMER_DEACTIVATED