Introductie
SDK's & integraties
Officiële clientbibliotheken voor de Puntjes API. Ze halen je OAuth2-tokens op en vernieuwen ze, proberen een call veilig opnieuw met een idempotency key, geven je getypeerde fouten terug en lopen je pagina's af. In je integratie schrijf je dus je eigen logica en geen HTTP-afhandeling. De experimentele Delphi-units doen dat niet: dat zijn alleen gegenereerde calls.
Officiële pakketten
| Pakket | Voor | Vereist |
|---|---|---|
puntjes/php-sdk | Elke PHP-applicatie: WooCommerce/WordPress-plugins, kassakoppelingen, cronjobs | PHP 8.1+ |
puntjes/laravel | Laravel-applicaties | PHP 8.2+, Laravel 12–13 |
Puntjes.Sdk | Elke .NET-applicatie: kassasoftware, webshops, achtergronddiensten | .NET 8 of .NET 10 |
com.github.PuntjesApp:puntjes-java | Elke Java-applicatie: kassasoftware, webshopbackends, achtergronddiensten | Java 17+ |
@puntjes/sdk | Node.js-servers: webshopbackends, serverless functies, kassakoppelingen | Node 20+ |
puntjes-delphi | Delphi- en Free Pascal-programma's, experimenteel | mORMot 2.4 |
puntjes/laravel is een dunne laag bovenop de core SDK: configuratie, een facade en een token store in je cache, zodat je applicatie één token grant per uur doet in plaats van één per request.
De PHP-pakketten staan op Packagist en volgen SemVer. Beide staan op 2.0: pin ze met ^2.0. Na een minor- of patchrelease van puntjes/php-sdk volgt vanzelf een release van puntjes/laravel met dezelfde calls. Bij een nieuwe major van de core wacht die release tot wij het Laravel-pakket ertegen hebben nagekeken.
Een klant registreren
De identifiers-array accepteert email en loyalty_card, en je mag ze volledig weglaten
zodat Puntjes zelf de kaart uitgeeft. Dat is de kortste juiste oproep en wat de meeste
integraties willen.
Stuur je wel een loyalty_card mee, dan moet die waarde een kaart zijn die de handelaar
heeft laten drukken en nog aan niemand gaf. Zie Klant-endpoints.
Op puntjes/php-sdk onder 1.0.0 werkt geen van beide: de oproep zonder kaart wordt
geweigerd voor er een request vertrekt, en de SDK kent geen loyalty_card als
identifiertype. Werk bij in plaats van eromheen.
Gewoon PHP (elk framework)
composer require puntjes/php-sdk:^2.0
De SDK praat PSR-18 en gebruikt de HTTP-client die je project al heeft. Ze installeert er zelf geen. Heb je er geen:
composer require guzzlehttp/guzzle
Maak je credentials aan in het beheerportaal (zie API-clients beheren). Het secret zie je één keer.
use Puntjes\Generated\Customers\Lookup\LookupRequestBuilderGetRequestConfiguration as Lookup;
use Puntjes\Generated\Models\CreateTransactionData;
use Puntjes\Puntjes;
$puntjes = Puntjes::make(getenv('PUNTJES_CLIENT_ID'), getenv('PUNTJES_CLIENT_SECRET'));
// Een klant scant zijn klantenkaart aan de kassa.
$customer = $puntjes->customers()->lookup()->get(new Lookup(
queryParameters: Lookup::createQueryParameters(identifier: $scannedCard),
))->wait()->getData();
echo $customer->getWalletBalance();
// Registreer de aankoop. Bedragen zijn in centen.
$body = new CreateTransactionData();
$body->setIdentifier($scannedCard);
$body->setTotalAmount(4200);
$body->setIdempotencyKey('order-'.$orderId);
$transaction = $puntjes->transactions()->post($body)->wait()->getData();
echo $transaction->getPointsEarned();
Puntjes::make() geeft een client terug die uit het OpenAPI-document van Puntjes is gegenereerd. Hij heeft dus elke call die de API heeft. Een call volgt de URL: ->customers()->byCustomer('42')->wallet()->get() is GET /customers/42/wallet.
Elke call geeft een promise terug. Sluit hem af met ->wait() en lees dan het data-deel van het antwoord met ->getData(). Een geslaagd antwoord zonder data-deel geeft null.
De SDK regelt de authenticatie: ze haalt bij de eerste call een client_credentials-token op, houdt het in cache tot het vervalt, en haalt één keer een nieuw token op als de API het oude afwijst.
Fouten
Elke exception erft van Puntjes\Exception\PuntjesException. Een foutantwoord van de API wordt een exception met een eigen naam, en errorCode() geeft de foutcode als enum:
use Puntjes\Exception\{ApiException, NotFoundException, ValidationException};
use Puntjes\Generated\Errors\ErrorCode;
try {
$puntjes->redemptions()->post($body)->wait();
} catch (NotFoundException $e) {
// 404: die klant of beloning bestaat niet
} catch (ValidationException $e) {
$e->errorsFor('reward_id');
} catch (ApiException $e) {
if ($e->errorCode() === ErrorCode::InsufficientBalance) {
// ...
}
$e->requestId(); // geef dit door aan de support
}
Is een code nieuwer dan je SDK, dan is errorCode() null en geeft code() nog altijd de ruwe tekst. Een 422 voor een fout veld is een ValidationException. Een 422 voor een bedrijfsregel, zoals INSUFFICIENT_BALANCE, is een ApiException.
Apple wallet-pass
WalletPass::apple($puntjes, $customerId) geeft de Apple-pass (een .pkpass-bestand) terug als stream:
use Puntjes\WalletPass;
$pass = WalletPass::apple($puntjes, $customerId);
Overstappen van 1.x
2.0 heeft een nieuwe kern, en de 1.x-calls ($puntjes->customers->lookup(), Puntjes\Request\*) bestaan niet meer. De upgradegids zet elke 1.x-call naast zijn 2.0-vorm.
1.x krijgt beveiligingsfixes tot 6 maanden nadat 2.0.0 op Packagist staat. Ze komen op de branch 1.x. Wil je op 1.x blijven, laat dan "puntjes/php-sdk": "^1.7" in je composer.json staan.
Laravel
composer require puntjes/laravel
PUNTJES_CLIENT_ID=your-client-id
PUNTJES_CLIENT_SECRET=your-client-secret
PUNTJES_BASE_URL=https://puntjes.app/api/v1
Meer is er niet aan. Laravel vindt de service provider en de facade vanzelf, en de tokens staan in de cache store van je applicatie.
use Puntjes\Generated\Customers\Lookup\LookupRequestBuilderGetRequestConfiguration as Lookup;
use Puntjes\Generated\Models\CreateTransactionData;
use Puntjes\Laravel\Facades\Puntjes;
$customer = Puntjes::customers()->lookup()->get(new Lookup(
queryParameters: Lookup::createQueryParameters(identifier: $card),
))->wait()->getData();
$body = new CreateTransactionData();
$body->setIdentifier($card);
$body->setTotalAmount($order->total_cents);
$body->setIdempotencyKey('order-'.$order->id);
Puntjes::transactions()->post($body)->wait();
De facade geeft elke call door aan de gegenereerde client van de core, dus elke call hierboven werkt ook via de facade. Je kunt Puntjes\Generated\PuntjesClient ook uit de container injecteren. Kom je van 1.x? De UPGRADING.md van het pakket zegt wat je moet aanpassen. Zie de README van het pakket voor configuratieopties, eigen HTTP-clients en testadvies.
WordPress / WooCommerce
Bouw rechtstreeks op puntjes/php-sdk: het heeft bewust geen harde dependency op Guzzle of op een framework, dus je kunt het veilig in een plugin meeleveren. Twee dingen tellen zodra je die plugin verdeelt:
- Scope je dependencies met PHP-Scoper of Strauss, zodat je gebundelde bibliotheken niet kunnen botsen met andere plugins in het gedeelde WordPress-proces.
- Bewaar tokens in een transient, zodat niet elke paginalading een token grant doet. De SDK-README bevat een kant-en-klare
TransientTokenStore-implementatie en de volledige richtlijnen.
.NET
dotnet add package Puntjes.Sdk
using Puntjes.Sdk;
using Puntjes.Sdk.Generated.Models;
var puntjes = PuntjesClientFactory.Create(
Environment.GetEnvironmentVariable("PUNTJES_CLIENT_ID")!,
Environment.GetEnvironmentVariable("PUNTJES_CLIENT_SECRET")!);
// Een klant scant zijn klantenkaart aan de kassa.
var customer = (await puntjes.Customers.Lookup.GetAsync(c => c.QueryParameters.Identifier = scannedCard))?.Data;
// Registreer de aankoop. Bedragen zijn in centen.
var transaction = (await puntjes.Transactions.PostAsync(new CreateTransactionData
{
Identifier = scannedCard,
TotalAmount = 4200,
IdempotencyKey = $"order-{orderId}",
}))?.Data;
Net als bij de PHP SDK is de client gegenereerd uit het OpenAPI-document en volgt een call de URL:
puntjes.Customers["42"].Wallet.GetAsync() is GET /customers/42/wallet. Tokens, retries en fouten werken zoals
bij de PHP SDK hieronder. Een foutantwoord wordt een subklasse van PuntjesException, zoals NotFoundException of
ValidationException, met ErrorCode, RawCode en RequestId. Paginator.AllAsync() loopt de pagina's van een
lijst af, en WalletPass.AppleAsync() geeft de Apple-pass terug als bytes. In de
README vind je alle opties.
Puntjes.Sdk staat op nuget.org en volgt SemVer. Krijgt de API een
nieuwe minor- of patchversie, dan volgt vanzelf een nieuwe pakketversie.
Java
Voeg de JitPack-repository en de dependency toe. De SDK vraagt Java 17 of nieuwer.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.PuntjesApp</groupId>
<artifactId>puntjes-java</artifactId>
<version>1.0.0</version>
</dependency>
Met Gradle zet je maven { url 'https://jitpack.io' } bij repositories en implementation 'com.github.PuntjesApp:puntjes-java:1.0.0' bij dependencies.
import app.puntjes.sdk.IdempotencyKey;
import app.puntjes.sdk.PuntjesClientFactory;
import app.puntjes.sdk.generated.models.CreateTransactionData;
var puntjes = PuntjesClientFactory.create(
System.getenv("PUNTJES_CLIENT_ID"), System.getenv("PUNTJES_CLIENT_SECRET"));
// Een klant scant zijn klantenkaart aan de kassa.
var customer = puntjes.customers().lookup().get(c -> c.queryParameters.identifier = scannedCard).getData();
// Registreer de aankoop. Bedragen zijn in centen.
var purchase = new CreateTransactionData();
purchase.setIdentifier(scannedCard);
purchase.setTotalAmount(4200);
purchase.setIdempotencyKey(IdempotencyKey.newKey());
var transaction = puntjes.transactions().post(purchase).getData();
De client is gegenereerd uit het OpenAPI-document, dus een call volgt de URL:
puntjes.customers().byCustomer("42").wallet().get() is GET /customers/42/wallet. Tokens en retries werken zoals bij de PHP SDK hierboven. IdempotencyKey.newKey() maakt een key voor een POST.
Elke fout is een unchecked PuntjesException. Een foutantwoord wordt een subklasse, zoals NotFoundException, ValidationException of RateLimitException. errorCode() geeft de foutcode als Optional, en requestId() geeft het request-id. ValidationException heeft daarnaast details(), de meldingen per veld, en errorsFor("reward_id"), de meldingen van één veld. Een 422 voor een bedrijfsregel, zoals INSUFFICIENT_BALANCE, is een gewone PuntjesException.
Paginator.all() loopt de pagina's van een lijst af als een lazy Stream, en WalletPass.apple() geeft de Apple-pass terug als bytes. In de README vind je alle opties.
JitPack levert com.github.PuntjesApp:puntjes-java, en het volgt SemVer. Het staat niet op Maven Central.
TypeScript en Node.js
npm install @puntjes/sdk
import { createPuntjes } from '@puntjes/sdk';
const puntjes = createPuntjes({
clientId: process.env.PUNTJES_CLIENT_ID!,
clientSecret: process.env.PUNTJES_CLIENT_SECRET!,
});
// Een klant scant zijn klantenkaart aan de kassa.
const { data: customer } = await puntjes.lookupCustomer({ query: { identifier: scannedCard } });
// Registreer de aankoop. Bedragen zijn in centen.
const { data: transaction } = await puntjes.submitTransaction({
body: { identifier: scannedCard, total_amount: 4200, idempotency_key: `order-${orderId}` },
});
De client is gegenereerd uit het OpenAPI-document, dus elk endpoint is een methode met getypeerde path, query en
body. Een call geeft { data, request, response } terug, en het eigen data-veld van de API zit daarbinnen: het
record is dus customer.data. Tokens, retries en fouten werken zoals bij de PHP SDK hierboven. Bij een foutantwoord gooit
de client een subklasse van PuntjesError, zoals NotFoundError of ValidationError, met code, rawCode en
requestId; RateLimitError heeft retryAfter, in milliseconden. paginate() loopt de pagina's van een lijst af, en walletPass.apple() geeft de Apple-pass terug als
bytes. In de README vind je alle opties.
Het pakket is een ES-module voor Node 20 of nieuwer. Gebruik het alleen op een server: met het client secret heb je
volledige toegang tot de API van je zaak, dus het hoort nooit in een browser. @puntjes/sdk staat op
npm en volgt SemVer. Elke versie na 1.0.0 publiceert onze CI met provenance. Krijgt de API een nieuwe minor- of patchversie,
dan volgt vanzelf een nieuwe pakketversie.
Delphi (experimenteel)
Download de twee units uit de laatste release, of installeer ze met Boss:
boss install github.com/PuntjesApp/puntjes-delphi
http := TJsonClient.Create('https://puntjes.app', 'api/v1');
puntjes := TPuntjesClient.Create(http);
http.SetBearer(accessToken);
writeln(puntjes.LookupCustomer(card).Data.WalletBalance);
mORMot 2 mopenapi genereert de units uit het OpenAPI-document. Je hebt mORMot 2 op tag 2.4-stable nodig in je
zoekpad. De units zijn experimenteel: onze CI compileert ze met Free Pascal, maar met de eigen compiler van Delphi is
dat nog niet gebeurd. Een veld dat leeg kan zijn of meer dan één vorm heeft, komt binnen als variant. Een update stuurt elk veld mee: een veld dat je niet invult, gaat als null naar de API en wordt dan leeggemaakt. Neem dus elk veld dat je wilt houden over in de payload. Een
antwoordtype zoals TDtoPuntjes53 kan in elke release een ander nummer krijgen. Lees dus .Data in de
expressie, en declareer dat type niet zelf. Anders dan de SDK's hierboven halen de units geen token op en vernieuwen
ze het niet, proberen ze niets opnieuw en lopen ze geen pagina's af: dat doet je eigen code. In de
README staat een volledig programma, met de tokenrequest.
Wat de SDK voor je afhandelt
- Tokens: ophalen, cachen en opnieuw ophalen van
client_credentials-tokens, met een token store die je per platform zelf invult. - Veilige retries: een
GET,PUT,PATCHofDELETEgaat met exponential backoff opnieuw de deur uit na een verbroken verbinding, een 5xx of een429 RATE_LIMITED. EenPOSTgaat alleen opnieuw als de body eenidempotency_keyheeft, want de API geeft bij een herhaalde key het eerste antwoord terug. De SDK maakt geen key voor je aan: zet er een op elkePOSTdie punten verplaatst, metIdempotencyKey::new()of met een waarde uit je eigen gegevens, bv.'order-'.$orderId. EenPOSTzonder key probeert ze nooit opnieuw. - Getypeerde fouten: elke foutcode komt terug als een exception met een eigen naam, met de machineleesbare code, de HTTP-status en de
request_iderbij. Bij een rate limit staatretryAfter()klaar. - Paginering:
Paginator::of()loopt de pagina's van een lijst af en vraagt de volgende pagina pas op wanneer je lus ze nodig heeft:
use Puntjes\Generated\Products\ProductsRequestBuilderGetRequestConfiguration as Products;
use Puntjes\Pagination\Paginator;
$all = Paginator::of(fn (int $page) => $puntjes->products()->get(new Products(
queryParameters: Products::createQueryParameters(page: $page),
))->wait());
foreach ($all as $product) {
echo $product->getExternalId();
}
Het OpenAPI-document
In PuntjesApp/puntjes-openapi staat het OpenAPI-document van elke versie van de API die Puntjes uitbracht. Een versie komt daar terecht op het moment dat Puntjes ze uitbrengt. Elke versie krijgt een tag, zoals v1.2.0, en een stukje in de CHANGELOG.md van de repository.
| Wat er verandert | De versie |
|---|---|
| Een bestaande client werkt niet meer: een endpoint valt weg, een veld dat in elk antwoord zat valt weg, een input wordt verplicht | Major (2.0.0) |
| Elke andere wijziging aan de API: een nieuw endpoint, een veld, iets dat uitgefaseerd wordt | Minor (1.3.0) |
| Alleen een beschrijving of een voorbeeld verandert | Patch (1.2.1) |
Genereer je zelf een client, lees api.json dan op een tag en niet op main. Zo verandert je client pas wanneer jij naar een nieuwere tag overstapt.
Andere talen
PHP, .NET, Java en TypeScript hebben vandaag een officiële SDK, en voor Delphi zijn er experimentele units. Vanuit elke andere taal werk je rechtstreeks tegen de REST API, of je genereert een client uit het OpenAPI-document. Elk endpoint is gewone JSON over OAuth2, en de referentiepagina's in deze documentatie beschrijven de vorm van elke request en elke response.