Handleidingen
Partnerintegraties
Een partner is een bedrijf met een app (een kassa, een webshop) die voor veel handelaars met Puntjes koppelt. Zonder deze flow maakt elke handelaar een API-client in het portaal en kopieert hij het secret naar je app. Met deze flow klikt de handelaar één keer op Toestaan in Puntjes, en je app haalt zelf een API-client van die handelaar op.
De flow is OAuth2 authorization code met PKCE, gevolgd door één aanroep van de partner-API. Daarna is je app een gewone integratie van de handelaar en gebruikt ze de client credentials grant, zoals Authenticatie uitlegt.
Wat je van Puntjes krijgt
Puntjes registreert je app als partner en geeft je:
- een partnerclient-ID;
- voor een confidential client (een server die een geheim kan bewaren) een client secret, één keer getoond. Bewaar het meteen: Puntjes houdt alleen een hash bij;
- voor een public client (een app op een toestel dat geen geheim kan bewaren) helemaal geen secret.
Je geeft Puntjes 1 tot 10 terugstuuradressen: volledige https-adressen zonder * en zonder #. De redirect_uri die je later stuurt, moet daar letter voor letter een van zijn. Een public client kan ook een loopback-adres opgeven, zoals Een desktop-app uitlegt.
Je kunt Puntjes ook een koppeladres geven: een volledig https-adres van de pagina op je site waar een handelaar de koppeling start. De partnerpagina in het Puntjes-portaal krijgt dan een knop Koppelen die de beheerder van de handelaar daarheen stuurt. Zonder koppeladres opent de knop je website.
De partnerclient kan de API van een handelaar niet gebruiken. Hij start alleen koppelingen.
1. Stuur de handelaar naar Puntjes
Open deze URL in de browser van de handelaar:
GET https://puntjes.app/oauth/authorize
?response_type=code
&client_id=JOUW_PARTNERCLIENT_ID
&redirect_uri=https://app.example.com/puntjes/callback
&state=JOUW_STATE
&code_challenge=JOUW_CODE_CHALLENGE
&code_challenge_method=S256
&branch=kerkstraat
| Parameter | Verplicht | Beschrijving |
|---|---|---|
response_type | Ja | Altijd code |
client_id | Ja | Je partnerclient-ID |
redirect_uri | Ja | Een van je geregistreerde terugstuuradressen, exact. Hoogstens 2000 tekens |
state | Ja | Een willekeurige waarde die je voor deze browser bijhoudt. Puntjes stuurt ze ongewijzigd terug. Hoogstens 500 tekens |
code_challenge | Ja | BASE64URL(SHA256(code_verifier)), zonder = op het einde. Hoogstens 128 tekens |
code_challenge_method | Ja | Altijd S256 |
branch | Nee | De external_id van een vestiging van de handelaar. Puntjes kiest die alvast op het toestemmingsscherm. Hoogstens 64 tekens |
De code_verifier is een willekeurige reeks van 43 tot 128 tekens uit A-Z, a-z, 0-9, -, ., _ en ~. Bewaar hem bij de state: je stuurt hem in stap 3.
Stuur altijd code_challenge_method=S256 mee
Ontbreekt code_challenge_method, dan neemt de OAuth-server plain, en dat weigert Puntjes. Een request zonder
methode of met plain komt terug op je terugstuuradres met error=invalid_request, net als een request zonder
code_challenge van een confidential client. Een public client zonder code_challenge, een code_challenge die
niet uit 43 tot 128 tekens uit A-Z, a-z, 0-9, -, ., _ en ~ bestaat, of een andere methode dan S256
en plain krijgt een fout in de browser (400 invalid_request) en wordt niet teruggestuurd.
Stuur geen scope mee: Puntjes kent geen scopes, en een request met een scope komt terug met error=invalid_scope. Stuur ook geen prompt=none: de handelaar moet het scherm zien, en Puntjes antwoordt met error=consent_required.
Een request met een onbekend client-ID, een terugstuuradres dat niet geregistreerd is of een parameter boven zijn limiet vertrouwt Puntjes niet. Dan toont Puntjes een fout in de browser en stuurt het niet terug, want het weet niet zeker dat het adres van jou is.
De handelaar logt in als dat nodig is en ziet de naam van je app op het toestemmingsscherm. Hij kiest een standaardvestiging, of geen, en klikt op Toestaan of Weigeren. Een branch die je meestuurt, kiest die vestiging alleen alvast: de handelaar kan een andere kiezen. Past de sleutel bij geen actieve vestiging, dan negeert Puntjes hem en staat de eerste actieve vestiging gekozen. Alleen een beheerder van de handelaar die de API-clients mag beheren, kan toestaan.
2. Lees de redirect
Na Toestaan komt de browser terug op je redirect_uri met drie parameters:
https://app.example.com/puntjes/callback?code=…&state=JOUW_STATE&connection=…
| Parameter | Beschrijving |
|---|---|
code | De authorization code voor stap 3 |
state | Je state, ongewijzigd. Vergelijk ze met de waarde die je bijhield, en stop als ze verschilt |
connection | Het kenmerk van deze koppeling, voor stap 4. Het werkt 60 minuten |
Heeft je terugstuuradres al een querystring, dan blijft die staan; Puntjes zet de drie parameters erachter.
Na Weigeren komt de browser terug met error=access_denied en je state, en Puntjes bewaart niets.
Hou deze URL uit je logs
De redirect-URL bevat de code en het kenmerk connection. Samen met een token leveren die een API-client van de
handelaar op. Schrijf de volledige URL niet weg in een log, een analysetool of een foutrapport.
3. Ruil de code in voor een token
Stuur de code snel naar het token-endpoint: een authorization code werkt 10 minuten en maar één keer.
curl -X POST https://puntjes.app/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d client_id=JOUW_PARTNERCLIENT_ID \
-d client_secret=JOUW_PARTNERCLIENT_SECRET \
-d redirect_uri=https://app.example.com/puntjes/callback \
-d code=DE_CODE \
-d code_verifier=JOUW_CODE_VERIFIER
redirect_uri is dezelfde waarde als in stap 1. Een confidential client stuurt zijn client_secret mee; een public client laat het weg. Het antwoord bevat een access_token dat 60 minuten werkt. Er is geen refresh token: het token dient alleen voor stap 4, en de volgende koppeling begint weer bij stap 1.
4. Haal de API-client op
curl -X POST https://puntjes.app/api/v1/partner/clients \
-H "Authorization: Bearer JOUW_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"connection":"HET_KENMERK","label":"Kassa 1"}'
label is de naam die de handelaar voor deze client in het portaal ziet, hoogstens 100 tekens. Het antwoord is 201 met de nieuwe client:
{
"data": {
"client_id": "9f3c2a6e-4b1d-4c8e-9a7f-2d5e8b1c0a43",
"client_secret": "JOUW_NIEUWE_CLIENT_SECRET",
"vendor": { "id": 7, "name": "Bakkerij Peeters" },
"branch": { "external_id": "kerkstraat", "name": "Kerkstraat", "type": "physical" }
}
}
branch is de standaardvestiging die de handelaar koos, of null. Bewaar client_secret meteen.
Opnieuw proberen is veilig. Stuur dezelfde connection opnieuw binnen haar 60 minuten en je krijgt dezelfde client_id en client_secret, ook als twee aanroepen tegelijk binnenkomen. Puntjes maakt één client per koppeling. Na 60 minuten antwoordt het kenmerk CONNECTION_EXPIRED, en CONNECTION_NOT_FOUND zodra de nachtelijke opruiming de koppeling heeft gewist. In beide gevallen komt het secret niet meer mee.
De pagina Partner-endpoints heeft de volledige referentie.
Als de aanroep mislukt
Sommige weigeringen kan alleen de handelaar oplossen. Toon de handelaar wat hij moet doen, en probeer daarna opnieuw met hetzelfde kenmerk zolang het geldig is.
| Code | Status | Wie lost het op |
|---|---|---|
CLIENT_LIMIT_REACHED | 422 | De handelaar: hij trekt een API-client in in het portaal. Daarna probeer je opnieuw met hetzelfde kenmerk |
BRANCH_INACTIVE | 422 | De handelaar: hij opent de gekozen vestiging opnieuw en jij probeert opnieuw; of hij koppelt opnieuw |
VENDOR_PENDING, VENDOR_SUSPENDED, VENDOR_DEACTIVATED, VENDOR_INACTIVE | 403 | De handelaar, samen met Puntjes: zijn account mag de API niet gebruiken |
SUBSCRIPTION_EXPIRED | 403 | De handelaar: hij kiest een plan onder Facturatie |
CONNECTION_EXPIRED | 422 | Niemand: het kenmerk is ouder dan 60 minuten. De handelaar koppelt opnieuw |
CONNECTION_NOT_FOUND | 404 | Niemand: de handelaar koppelt opnieuw |
VALIDATION_ERROR | 422 | Jij: connection of label ontbreekt of is te lang |
UNAUTHENTICATED | 401 | Jij: het token ontbreekt of is verlopen, of je toegang als partner is ingetrokken |
INVALID_CLIENT | 401 | Jij: het token is geen partnertoken uit stap 3, bv. een token uit client credentials |
CONNECTION_NOT_FOUND geeft bewust één antwoord voor verschillende oorzaken: een onbekend kenmerk, een kenmerk van een andere partner, een kenmerk dat een andere gebruiker toestond dan de gebruiker van het token, een gebruiker die geen beheerder van de handelaar meer is, een client die de handelaar introk nadat je hem ophaalde, of een verlopen kenmerk na de nachtelijke opruiming. Een fout kenmerk verraadt dus niet welke controle mislukte.
Een desktop-app
Een desktop-app kan geen geheim bewaren, en ze kan geen redirect op een https-adres ontvangen. Ze gebruikt daarom een listener op dezelfde computer (RFC 8252, sectie 7.3). Doe dit:
- Laat Puntjes je app registreren als public client met een loopback-terugstuuradres:
http://127.0.0.1/puntjes/callbackofhttp://[::1]/puntjes/callback. Laat de poort weg. In stap 1 mag je dan elke poort sturen, en het pad moet hetzelfde blijven. In productie weigert Puntjeshttp://localhost, omdat een naam naar een ander adres kan wijzen. - Luister alleen op het loopback-adres dat je registreerde (
127.0.0.1of[::1]), nooit op alle netwerkadressen van de computer. - Open voor stap 1 de standaardbrowser van het systeem, geen webweergave in je app (RFC 8252, sectie 8.12).
- Neem één request aan op de listener. Antwoord met een pagina op een adres zonder de query, zodat de
codeuit de adresbalk verdwijnt. Stop daarna de listener. - Stuur in stap 3 geen
client_secret. - Stuur in stap 3 dezelfde
redirect_urials in stap 1, met dezelfde poort. Een andere poort geeft400 invalid_request. - Hou de redirect-URL uit je logs: hij bevat de
codeen het kenmerkconnection.
Limieten
| Wat | Limiet |
|---|---|
POST /api/v1/partner/clients | 30 aanroepen per minuut per partnerclient; 30 per minuut per adres zonder geldig token |
POST /api/v1/client/secret en DELETE /api/v1/client | 10 aanroepen per minuut per client voor de twee samen; 10 per minuut per adres zonder geldig token |
GET /oauth/authorize | 30 requests per minuut per browseradres, of per aangemelde gebruiker |
POST /oauth/token | 60 requests per minuut per client-ID vanaf één IP-adres; 600 per minuut per IP-adres |
| Authorization code | 10 minuten, één keer bruikbaar |
| Access token uit de code | 60 minuten, geen refresh token |
Kenmerk connection | 60 minuten |
Boven een limiet antwoordt de partner-API 429 RATE_LIMITED.
Na de koppeling
De client die je ophaalde, is een gewone API-client van de handelaar. Vraag een token met de client credentials grant (zie Authenticatie) en roep daarmee de API van de handelaar aan. De client mag de hele zaak gebruiken: elke vestiging, elke klant. De vestiging uit de koppeling is alleen de standaard voor een request zonder branch.
De handelaar ziet de client in het portaal onder API-clients, met je label, en kan hem daar intrekken.
Je app kan zonder de handelaar iets met de client doen. Is het secret misschien uitgelekt, dan vraagt ze een nieuw met POST /api/v1/client/secret. Wordt de app opnieuw geïnstalleerd of niet meer gebruikt, dan verwijdert ze de client met DELETE /api/v1/client, zodat oude clients het maximum aantal API-clients van de handelaar niet opvullen. Beide aanroepen vragen een token van de client en zijn client_secret; Partner-endpoints geeft de details.