Ervaringen met het Tikkie API Portal
Auteur: Jan Willem Teunisse, 15 oktober 2020 (edited 4-3-2021)
Inleiding
Bij een goede doelen organisatie ontstond de behoefte om bij de verkoop van bepaalde artikelen in de e-factuur een zgn. Tikkie op te nemen. Uit de ervaringen van andere organisaties is gebleken dat door gebruik te maken van een Tikkie de facturen sneller worden betaald.
Voor mij een belangrijke reden om uit te zoeken hoe we dit kunnen implementeren. Dit artikel beschrijft in het kort mijn ervaringen om tot een werkende uitbreiding van het factureringssysteem te komen.
Het factureringssysteem is een eenvoudig systeem, bestaande uit een tweetal Excel bestanden, een Word factuur sjabloon en een simpel script dat
de PDF e-facturen aanmaakt en vervolgens per e-mail verzend aan de afnemer van het artikel (1 of meer stuks).
De Excel bestanden kun je respectievelijk vergelijken met een datatabel met de Naam, Adresgegevens, Telefoon en E-mailadres van de afnemer,
de andere tabel bevat de logistieke gegevens zoals welke artikelen, de aantallen per artikel, de totale prijs, de bestel-, factuur en betaaldatum
en eventuele opmerkingen aangaande de levering, e.d.
Het doel is om alle facturen, waarvan het factuurbedrag beneden € 500,- ligt, te voorzien van een Tikkie link, zowel in de PDF e-factuur als
in het e-mail bericht waarmee de e-factuur wordt verzonden.
Ervaringen: van de inmiddels 155 verzonden facturen zijn er 67 betaald met een Tikkie, een 42% en binnen 7 dagen.
In dit artikel maken we gebruik van Tikkie versie 2.1.2
Voorbereiding
Belangrijk is om je eerst in te lezen in wijze waarop Tikkie voor bedrijven werkt.
Aangezien Tikkie is ontwikkeld door ABN AMRO en de goede doelen organisatie gebruikmaakt van een ABN AMRO bankrekening, ligt het voor de hand om
Tikkie te gaan gebruiken.
Meld je vervolgens als organisatie aan bij:
- het Tikkie portaal: Tikkie Me en vraag een Tikkie voor bedrijven account aan;
- het ABN AMRO developers platform ;
Lees ook goed de Tutorials door op dit developers platform. Op Github zijn ook voorbeelden te vinden Tikkie implementaties met PHP, Ruby, Python en C#, maar volgens mij zijn die gebaseerd op de vorige Tikkie versie.
In dit artikel beschrijf ik een implementatie met het Windows object WinHttp.WinHttpRequest, mede omdat de gebruikte Windows scriptingtaal dit object ondersteunt en ik er al enige ervaring in de loop van de jaren mee heb opgedaan.
Aan het eind van het artikel is nog een prototype met Go (Golang) opgenomen. Tijdens het afronden van het artikel werd ik geattendeerd op het Android/iOS apps framework van Google genaamd Flutter en die andere programmeertaal van Google, namelijk Dart. Dus ook maar een Dart prototype gemaakt.
En eind februari '21 kwam ik bij toeval op het spoor van een cross IDE platform (ontwikkeltool) B4X.com van Anywhere Software waarmee je apps kan ontwikkelen voor Android, iOS, Windows,Linux en IoT microcomputers zoals Raspberry Pi, Arduino, ESP32, e.d. Om dit uit te proberen en ervaring op te doen, heb ik hiermee een Windows tooltje getTikkie gemaakt. Na het compileren van de broncode draait de getTikkie app met de Java JVM.
Prototype Create Tikkie Payment Request
Teneinde een Tikkie payment aan te kunnen maken heb je eerst het volgende nodig. Op het ABN AMRO developer platform maak je eerst een app naam aan.
Hiermee kun je vervolgens een API-Key ophalen uit je gegevens op het platform.
Deze API-Key gebruik je vervolgens om met een HTTPS POST request een app-token op te halen, zodat je vervolgens een Tikkie link met een HTTPS POST
request kunt ophalen om deze Tikkie URL in de e-factuur en e-mail op te nemen.
Hiervoor moet je de payment gegevens in JSON format mee sturen, bijvoorbeeld als:
{ "amountInCents": 4500, "description": "geleverde wijnen", "expiryDate": "2020-10-21", "referenceId": "HA.2020.123" }
Het description veld is in ieder geval verplicht.
Het prototype en het factureringsscript is geschreven in de Windows scripting taal Windows Interface Language, beter bekend onder de naam Winbatch. Voor mij al jaren mijn "Zwitsere zakmes" voor Windows omgevingen. De hieronder weer gegeven code kan ook makkelijk worden omgezet in VBA of VBscript.
Het hoofdgedeelte van het prototype
rTikkiePayReqToken = ""
rTikkieURL = ""
rTikkieCreationDate = ""
ToDay = TimeYmdHms()
AddDays = "0000:00:14:00:00:00" ; 14 dagen later
ExpireDate = TimeAdd(ToDay, AddDays)
ExpireDate = StrSub(ExpireDate, 1, 10)
ExpireDate = Strreplace(ExpireDate , ":", "-")
Test_URL_Token = "https://api-sandbox.abnamro.com/v2/tikkie/sandboxapps"
Test_URL = "https://api-sandbox.abnamro.com/v2/tikkie/paymentrequests"
Prod_URL = "https://api.abnamro.com/v2/tikkie/paymentrequests"
TikkieAppName = "myappTikkie"
rTikkieAppToken = "..." ;
TikkieApiKey = "MY-API-Key" ; The API key that allows the usage of the Tikkie API.
; JSON body fields
TikkieAmountCents = "4500" ; in euro centen
TikkieDescr = "Geleverde wijnen" ; bijv. 'geleverde wijnen, ZA.2020.nnn max. 35 tekens
TikkieExpDate = Expiredate ; format "jjj-mm-dd"
TikkieRefID = "HA.2020.123" ; format factuurnummer 'code.2020.nnn'
; eerst app-token aanmaken
tikkieOK = GetABNATikkieToken(Test_URL_Token, TikkieApiKey) ;; result rTikkieAppToken,
if tikkieOK then
Message("Get Tikkie Token Result", rTikkieAppToken)
ClipPut (rTikkieAppToken) ; kopieer naar klembord
else
Message("Get Tikkie Token Result error", rMsgTekst)
exit
endif
; nu dan de Tikkie link
tikkieOK = GetABNATikkieLink(Test_URL, TikkieApiKey, rTikkieAppToken, TikkieAmountCents, TikkieDescr, TikkieExpDate, TikkieRefID)
if tikkieOK then
Message("Get Tikkie Result", rTikkieURL)
ClipPut (rTikkieURL) ; kopieer naar klembord
else
Message("Get Tikkie Result error", rMsgTekst)
endif
exit
Met behulp van de developers platform Sandbox kunnen we nu het script uittesten of het werkt, zie hiervoor de waardes van de variabelen
Test_URL_Token en Test_URL.
Het eerste halen we de app-token op, zodat we vervolgens de Tikkie link kunnen opvragen.
Het resultaat in de vorm van de Tikkie link ziet eruit als https://sbx.tikkie.me/pay/tikkie-link.
Hieronder het gedeelte van een schermafdruk van de webbrowser met deze tikkie-link. U ziet bovenin de tekst 'Geleverde wijnen' uit het
veld description en het bedrag
wat dient te worden overgemaakt met behulp van een iDeal betaling.
Hieronder wordt nu ingegaan op de twee subroutines, zoals eerst voor het ophalen van het app-token en vervolgens die voor de Tikkie link.
Mijn POST prototype voor het ophalen van app-token
#DefineSubroutine GetABNATikkieToken(pURL, pTikkieApiKey)
oHTTP = ObjectCreate("WinHttp.WinHttpRequest.5.1")
;; eerst tikkie app-token verkrijgen met Post
oHTTP.Open("POST", pURL, @False)
oHTTP.SetRequestHeader("API-Key", pTikkieApiKey")
ErrorMode(@OFF)
LaatsteFout = LastError() ; reset
oHTTP.Send() ;; geen request body
LaatsteFout = LastError() ; pak de eventuele fout op
ErrorMode(@CANCEL)
if LaatsteFout == 0 then
PostResponse = oHTTP.ResponseText
PostStatus = oHTTP.Status ; check status
if PostStatus == "201" then
p = StrIndex(PostResponse, '"appToken":"', 1, @FWDSCAN)
e = StrIndex(PostResponse, '"}', p+12, @FWDSCAN)
rTikkieAppToken = StrSub(PostResponse, p+12, e-p-12)
rMsgTekst = ""
urlOK = @TRUE
else ;; fout Status == 400, 401, 403, 500
PostResponse = StrReplace(PostResponse, @TAB, "")
PostResponse = StrReplace(PostResponse, @LF, "")
PostResponse = StrReplace(PostResponse, " ", " ")
p = StrIndex(PostResponse, '"errors": [', 1, @FWDSCAN)
e = StrIndex(PostResponse, ']', p+11, @FWDSCAN)
rTikkieError = StrSub(PostResponse, p+11, e-p-11)
p = StrIndex(rTikkieError, '"message": "', 1, @FWDSCAN)
e = StrIndex(rTikkieError, '",', p+12, @FWDSCAN)
rTikkieError = StrSub(rTikkieError, p+12, e-p-12)
rMsgTekst = StrCat("GetABNATikkieToken - HTTP Post error: ", PostStatus, " ", rTikkieError)
; logFileAppend(logFileName, rMsgTekst, logDateTime)
urlOK = @FALSE
rTikkieAppToken = ""
endif
else ;; toch oHTTP error
rMsgTekst = StrCat("oHTTP.Send Post GetABNATikkieToken COM/OLE error: ", LaatsteFout)
; logFileAppend(logFileName, msgTekst, logDateTime)
urlOK = @FALSE
rTikkieAppToken = ""
endif
oHTTP = 0
return urlOK
#EndSubroutine ; GetABNATikkieToken
De parameters voor de subroutine zijn:
- pURL de URL van het Tikkie portal, voor de Sandbox ontwikkelomgeving https://api-sandbox.abnamro.com/v2/tikkie/sandboxapps
- pTikkieApiKey mijn API-Key voor deze omgeving
Het resultaat: in de variabele rTikkieAppToken vinden we de opgehaalde app-token terug in het voorbeeld formaat: 3321cd11-c18d-4164-b2f6-315e9aa62766
Eventuele ondervonden fouten worden in de else tak van het if PostStatus == "201" then statement afgehandeld.
De return variable urlOK geeft aan of het POST request succesvol was.
Met het app-token kunnen we nu het Tikkie Payment Request aanmaken. Hiervoor is verder nodig het Tikkie bedrag in euro centen,
een korte omschrijving van maximaal 35 tekens, de expiratiedatum (hoe lang blijft deze Tikkie actief) in het ISO format "jjjj-mm-dd"
en een referentie identificatie bijvoorbeeld in de vorm van een factuurnummer.
Mijn POST prototype voor het maken van het Tikkie Payment Request
#DefineSubroutine GetABNATikkieLink(pURL, pTikkieApiKey, pTikkieAppToken, pTikkieAmtCnts, pTikkieDescr, pTikkieExpDate, pTikkieRefID) ; ophalen uit Google calendar events en verwerken in Queue
oHTTP = ObjectCreate("WinHttp.WinHttpRequest.5.1")
;; eerst acces_token verkrijgen met Post
TikkieJSON = '{ "amountInCents": ' : pTikkieAmtCnts : ','
TikkieJSON = TikkieJSON : ' "description": "' : pTikkieDescr : '", '
TikkieJSON = TikkieJSON : ' "expiryDate": "' : pTikkieExpDate : '", '
TikkieJSON = TikkieJSON : ' "referenceId": "' : pTikkieRefID : '" }'
oHTTP.Open("POST", pURL, @False)
oHTTP.SetRequestHeader("API-Key", pTikkieApiKey")
oHTTP.SetRequestHeader("X-App-Token", pTikkieAppToken")
oHTTP.SetRequestHeader("Content-Type", "application/JSON")
ErrorMode(@OFF)
LaatsteFout = LastError() ; reset
oHTTP.Send(TikkieJSON)
LaatsteFout = LastError() ; pak de eventuele fout op
ErrorMode(@CANCEL)
if LaatsteFout == 0 then
PostResponse = oHTTP.ResponseText
PostStatus = oHTTP.Status ; check status
if PostStatus == "201" || PostStatus == "202" then
p = StrIndex(PostResponse, '"paymentRequestToken":"', 1, @FWDSCAN)
e = StrIndex(PostResponse, '","', p+23, @FWDSCAN)
rTikkiePayReqToken = StrSub(PostResponse, p+23, e-p-23)
p = StrIndex(PostResponse, '"url":"', e, @FWDSCAN)
e = StrIndex(PostResponse, '",', p+7, @FWDSCAN)
rTikkieURL = StrSub(PostResponse, p+7, e-p-7)
p = StrIndex(PostResponse, '"createdDateTime":"', e, @FWDSCAN)
e = StrIndex(PostResponse, '",', p+19, @FWDSCAN)
rTikkieCreationDate = StrSub(PostResponse, p+19, e-p-19)
rMsgTekst = ""
urlOK = @TRUE
else ;; foutje Status == 400, 401, 403, 500
PostResponse = StrReplace(PostResponse, @TAB, "")
PostResponse = StrReplace(PostResponse, @LF, "")
PostResponse = StrReplace(PostResponse, " ", " ")
p = StrIndex(PostResponse, '"errors": [', 1, @FWDSCAN)
e = StrIndex(PostResponse, ']', p+11, @FWDSCAN)
rTikkieError = StrSub(PostResponse, p+11, e-p-11)
p = StrIndex(rTikkieError, '"message": "', 1, @FWDSCAN)
e = StrIndex(rTikkieError, '",', p+12, @FWDSCAN)
rTikkieError = StrSub(rTikkieError, p+12, e-p-12)
rMsgTekst = StrCat("GetABNATikkieLink - HTTP Post error: ", PostStatus, " ", rTikkieError)
; logFileAppend(logFileName, rMsgTekst, logDateTime)
urlOK = @FALSE
endif
else ;; toch oHTTP error
rMsgTekst = StrCat("oHTTP.Send Post GetABNATikkieLink COM/OLE error: ", LaatsteFout)
; logFileAppend(logFileName, rMsgTekst, logDateTime)
urlOK = @FALSE
endif
oHTTP = 0
return urlOK
#EndSubroutine ; GetABNATikkieLink
De parameters voor de subroutine zijn:
- pURL de URL van het Tikkie portal, voor de Sandbox ontwikkelomgeving https://api-sandbox.abnamro.com/v2/tikkie/sandboxapps
- pTikkieApiKey mijn API-Key voor deze omgeving
- pTikkieAppToken het hiervoor opgehaalde app-token
- pTikkieAmtCnts, enz. de waarden voor het JSON body bericht.
Het resultaat: de variabele rTikkieURL bevat nu de door de Portal aangemaakte Tikkie link. Deze link kunnen we nu verder
gebruiken in ons factureringssysteem.
Eventuele ondervonden fouten worden in de else tak van het if PostStatus == "201" then statement afgehandeld.
De return variable urlOK geeft aan of het POST request succesvol was.
Problemen of fouten tijdens het ontwikkelen
Mocht je tijdens het ontwikkelen van je eigen versie ergens vastlopen, dan kun je ook met het CURL tool testen waar mogelijk het probleem of fout
wordt veroorzaakt.
De CURL code, met dank aan ABN AMRO Developer Support, is de volgende:
curl -X POST 'https://api-sandbox.abnamro.com/v2/tikkie/paymentrequests' \
-H 'API-Key: MY_API_KEY_HERE' \
-H 'X-App-Token: MY_APP_TOKEN_HERE' \
-H 'Content-Type: application/json' \
--data-raw '{ "amountInCents": 4500, "description": "geleverde wijnen", "expiryDate": "2020-10-21", "referenceId": "HA.2020.123" }'
Gereedmaken voor productie gebruik
Nadat je klaar bent met het testen en met behulp van de Sandbox in het developers platform uitproberen hoe het werkt,
kunnen we een productie autorisatie aanvragen.
Tijdens het wachten op productie goedkeuring kunnen we intussen de software code opschonen en documenteren.
Hierna worden de subroutines in ons factureringssysteem opgenomen en wordt een acceptatietest uitgevoerd, alvorens het voor de facturering te gaan gebruiken. Voor productie heb je alleen de API functie Create Payment Request nodig, in mijn geval dus de subroutine GetABNATikkieLink(...).
Nadat je van het ABN AMRO developer platform het groene licht heb gekregen, maak je op het Tikkie.me bedrijfsportaal het productie app-token aan en kopieer je de inhoud van het token op een beveiligde manier in je factureringssysteem. En kan je vervolgens je eerste facturen met een Tikkie aanmaken en verzenden.
Prototype test met Go (Golang)
Na het ontwikkelen van de interface met de Tikkie Portal met Winbatch, leek het me ook een aardig idee om een prototype te ontwikkelen voor de
taal Go (Golang).
Na het uitzoeken en ontwikkelen met Winbatch, bleek dit vrijwel appeltje-eitje te zijn.
Eerst vragen we met de Api-Key het app-token op en vervolgens de Tikkie URL. Verander wel de naam MY_API_KEY in de juiste Api-Key.
De code bevat een aantal print statements om het verloop van de code te volgen. Dit dient nog wel te worden opgeschoond.
package main
// testtikkie.go
// auteur: J.W. Teunisse, 10-10-2020
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
s "strings"
"time"
)
const TEST_URL_TOKEN = "https://api-sandbox.abnamro.com/v2/tikkie/sandboxapps"
const TEST_URL = "https://api-sandbox.abnamro.com/v2/tikkie/paymentrequests"
const PROD_URL = "https://api.abnamro.com/v2/tikkie/paymentrequests"
const TIKKIE_API_KEY = MY_API_KEY
var rTikkieAppToken string
var rTikkieURL string
var rMsgTekst string
var rTikkiePayReqToken string
func GetABNATikkieToken(pURL string, pTikkieApiKey string) bool {
var urlOK bool
// var postAddress string
var postBody string
var respBody, respStatus string
var p, e int
urlOK = true
postBody = ""
fmt.Println(" postBody: ", postBody)
req, err := http.NewRequest("POST", pURL, bytes.NewBuffer([]byte(postBody)))
if err != nil {
fmt.Println(" fatal Http Error reading NewRequest: ", err)
urlOK = false
rMsgTekst = "GetABNATikkieToken - HTTP Post Fatal error NewRequest"
return urlOK
}
// Set headers
req.Header.Set("API-Key", pTikkieApiKey)
// req.Header.Set("Connection", "Close")
// req.Header.Set("Content-Type", "application/json")
// req.Header.Set("Authorization", "Basic "+pAuthorBase64)
// Set client timeout
client := &http.Client{Timeout: time.Second * 10}
// Send request
resp, err := client.Do(req)
if err != nil {
fmt.Println(" Fatal Error reading response. ", err)
urlOK = false
rMsgTekst = "GetABNATikkieToken - HTTP Post Fatal error reading response " // +err
return urlOK
}
defer resp.Body.Close()
fmt.Println(" response Status:", resp.Status)
respStatus = resp.Status[0:3]
fmt.Println(" respStatus:", respStatus)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(" Fatal Error reading response body. ", err)
urlOK = false
rMsgTekst = "GetABNATikkieToken - HTTP Post Fatal error reading resp.Body"
return urlOK
} else {
respBody = string(body)
}
// fmt.Printf(" %s\n", body) // for testing
fmt.Println(" respBody = ", respBody)
if respStatus == "201" {
urlOK = true
p = s.Index(respBody, `"appToken":"`)
e = s.Index(respBody[p+11:], `"}`)
fmt.Println(" p, e = ", p, e)
rTikkieAppToken = respBody[p+12:e+p+11]
rMsgTekst = ""
} else {
urlOK = false
p = s.Index(respBody, `"message": "`)
e = s.Index(respBody[p+11:], `",`)
fmt.Println(" p, e = ", p, e)
rMsgTekst = "GetABNATikkieToken - HTTP Post Fatal error: "+respStatus+", "+respBody[p+12:e+p+11]
}
return urlOK
} // end of GetABNATikkieToken
func GetABNATikkieLink(pURL, pTikkieApiKey,pTikkieAppToken, pTikkieAmtCnts, pTikkieDescr, pTikkieExpDate, pTikkieRefID string) bool {
var urlOK bool
// var postAddress string
var postBody string
var respBody, respStatus string
var p, e int
urlOK = true
postBody = `{ "amountInCents": ` + pTikkieAmtCnts + `, "description": "` + pTikkieDescr + `", "expiryDate": "` + pTikkieExpDate + `", "referenceId": "` + pTikkieRefID + `" }`
fmt.Println(" postBody: ", postBody)
req, err := http.NewRequest("POST", pURL, bytes.NewBuffer([]byte(postBody)))
if err != nil {
fmt.Println(" fatal Http Error reading NewRequest: ", err)
urlOK = false
rMsgTekst = "GetABNATikkieLink - HTTP Post Fatal error NewRequest"
return urlOK
}
// Set headers
req.Header.Set("API-Key", pTikkieApiKey)
req.Header.Set("X-App-Token", pTikkieAppToken)
req.Header.Set("Content-Type", "application/json")
// Set client timeout
client := &http.Client{Timeout: time.Second * 10}
// Send request
resp, err := client.Do(req)
if err != nil {
fmt.Println(" Fatal Error reading response. ", err)
urlOK = false
rMsgTekst = "GetABNATikkieLink - HTTP Post Fatal error reading response " // +err
return urlOK
}
defer resp.Body.Close()
fmt.Println(" response Status:", resp.Status)
respStatus = resp.Status[0:3]
fmt.Println(" respStatus:", respStatus)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(" Fatal Error reading response body. ", err)
urlOK = false
rMsgTekst = "GetABNATikkieLink - HTTP Post Fatal error reading resp.Body"
return urlOK
} else {
respBody = string(body)
}
// fmt.Printf(" %s\n", body) // for testing
fmt.Println(" respBody = ", respBody)
if respStatus == "201" {
urlOK = true
p = s.Index(respBody, `"paymentRequestToken":"`)
e = s.Index(respBody[p+23:], `","`)
fmt.Println(" payReqToken p, e = ", p, e)
rTikkiePayReqToken = respBody[p+23:e+p+23]
p = s.Index(respBody, `"url":"`)
e = s.Index(respBody[p+7:], `","`)
fmt.Println(" url p, e = ", p, e)
rTikkieURL = respBody[p+7:e+p+7]
fmt.Println(" payReqToken, url: ", rTikkiePayReqToken, rTikkieURL)
rMsgTekst = ""
} else {
urlOK = false
p = s.Index(respBody, `"message": "`)
e = s.Index(respBody[p+11:], `",`)
fmt.Println(" p, e = ", p, e)
rMsgTekst = "GetABNATikkieLink - HTTP Post Fatal error: "+respStatus+", "+respBody[p+12:e+p+11]
}
return urlOK
} // end of GetABNATikkieLink
func main() {
fmt.Println("Start ABNA Tikkie test")
tikkieOK := GetABNATikkieToken(TEST_URL_TOKEN, TIKKIE_API_KEY)
if tikkieOK {
fmt.Println("Klaar: TikkieOK, app-token = ", rTikkieAppToken)
} else {
fmt.Println("Klaar: Tikkie Not OK, rMsgTekst = ", rMsgTekst)
}
// GetABNATikkieLink(pURL, pTikkieApiKey,pTikkieAppToken, pTikkieAmtCnts, pTikkieDescr, pTikkieExpDate, pTikkieRefID string)
tikkieOK = GetABNATikkieLink(TEST_URL, TIKKIE_API_KEY, rTikkieAppToken, "4194", "Geleverde wijnen", "2020-10-24", "factuurnr 2020.123")
if tikkieOK {
fmt.Println("Klaar: TikkieOK, Tikkie URL = ", rTikkieURL)
} else {
fmt.Println("Klaar: Tikkie Not OK, rMsgTekst = ", rMsgTekst)
}
}
Hieronder het resultaat. Voor de duidelijkheid zijn er extra lege regels in de uitvoer geplaatst.
D:\Ontwikkelomgeving\Go\src>go run testtikkie.go
Start ABNA Tikkie test
postBody:
response Status: 201 Created
respStatus: 201
respBody = {"appToken":"53c79bf6-5834-4fea-9217-0eb60cf7d648"}
p, e = 1 37
Klaar: TikkieOK, app-token = 53c79bf6-5834-4fea-9217-0eb60cf7d648
postBody: { "amountInCents": 4194, "description": "Geleverde wijnen", "expiryDate": "2020-10-24", "referenceId": "factuurnr 2020.123" }
response Status: 201 Created
respStatus: 201
respBody = {"paymentRequestToken":"nhmwkp4c9woyeQPFdvb3HN","amountInCents":4194,"referenceId":"factuurnr 2020.123","description":"Geleverde wijnen","url":"https://sbx.tikkie.me/pay/TSTEfDIAxJI/nhmwkp4c9woyeQPFdvb3HN","expiryDate":"2020-10-24","createdDateTime":"2020-10-10T13:24:42.898Z","status":"OPEN","numberOfPayments":0,"totalAmountPaidInCents":0}
payReqToken p, e = 1 22
url p, e = 137 60
payReqToken, url: nhmwkp4c9woyeQPFdvb3HN https://sbx.tikkie.me/pay/TSTEfDIAxJI/nhmwkp4c9woyeQPFdvb3HN
Klaar: TikkieOK, Tikkie URL = https://sbx.tikkie.me/pay/TSTEfDIAxJI/nhmwkp4c9woyeQPFdvb3HN
D:\Ontwikkelomgeving\Go\src>
Prototype test met Dart
Na de eerste kennismaking met Flutter en Dart leek me het een interessant idee om ook een Tikkie prototype met Dart te bouwen.
Na het inlezen, bestuderen van voorbeelden, bleek dit vanwege de onbekendheid met Dart iets lastiger te zijn dan met Go het geval was.
Maar vanaf het tijdstip van het installeren van Dart om mijn Windows PC was het ontwikkelen van een werkende versie met de Sandbox omgeving
binnen een uurtje of vier geklaard. De meeste tijd werd besteed aan het uitzoeken van hoe je met Dart programmeert.
Met kennis en ervaring met Java, C# en Javascript kom je er overigens snel uit.
Ook hier vragen we eerst met de Api-Key het app-token op en vervolgens de Tikkie URL. Verander wel de naam MY_API_KEY in de juiste Api-Key.
De code bevat een aantal print statements om het verloop van de code te volgen. Dit dient nog wel te worden opgeschoond en omgezet naar meer
bruikbare code voor een productie versie.
// file main.dart
// package tikkie
// author: Jan Willem Teunisse
// edited: 17-10-2020 , Version: 0.0.1
//
/// Package tikkie: contains methods to get Tikkie URL for creating a payment request
///
/// Description:
/// to do
///
import 'package:intl/intl.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
void main(List arguments) async {
const test_URL = "https://api-sandbox.abnamro.com/v2/tikkie/paymentrequests" ;
const test_URL_Token = "https://api-sandbox.abnamro.com/v2/tikkie/sandboxapps" ;
const prod_URL = "https://api.abnamro.com/v2/tikkie/paymentrequests" ;
const TikkieApiKey = "MY_API_KEY" ; // test of productie Api-Key
String TikkieAppToken = "todo" ;
String TikkieURL = "leeg" ;
String TikkieAmountCents = "500" ; // in euro centen
String TikkieDescr = "Sandbox test Tikkie" ; // bijv. 'geleverde wijnen, ZA.2020.nnn max. 35 tekens
String TikkieExpDate = "Expiredate" ; // format "jjj-mm-dd"
String TikkieRefID = "Test.factuur.nr" ; // format factuurnummer 'code.2020.nnn'
String tikkieBody ;
String TikkieError ;
String respValues, status ;
int p, e ;
var now = new DateTime.now();
var expDate = now.add(Duration(days: 14)); // add 14 days
var formatter = new DateFormat('yyyy-MM-dd');
TikkieExpDate = formatter.format(expDate);
print("Start testing the Tikkie https post request:\r\n");
tikkieBody = '{ "amountInCents": ' + TikkieAmountCents + ', "description": "' + TikkieDescr + '", "expiryDate": "'+ TikkieExpDate +
'", "referenceId": "' + TikkieRefID + '" } ' ;
print(" body: $tikkieBody") ;
// nu http package gebruiken
// Sending a POST request with headers, first the sandbox app-token
var headers = {'API-Key': TikkieApiKey};
http.Response responseTkn = await http.post(test_URL_Token, headers: headers, body: '') ; // payload);
print(responseTkn.statusCode); // 201
var statusCode = responseTkn.statusCode ;
respValues = responseTkn.body.toString() ;
print("respValues: $respValues") ;
Map jsonData = json.decode(responseTkn.body) as Map;
if (statusCode == 201) {
TikkieAppToken = jsonData['appToken'] ;
print("201 app-token: $TikkieAppToken") ;
} else {
p = respValues.indexOf('"message": "', 1) ;
e = respValues.indexOf('",', p+12) ;
TikkieError = respValues.substring(p+12, e) ;
status = statusCode.toString() ;
print(" Error Statuscode $status, message: $TikkieError") ;
print(" Test stopt hier ...") ;
exit(1) ;
}
// Sending nu POST to create Tikkie payment request
headers = {'API-Key': TikkieApiKey, 'X-App-Token': TikkieAppToken, 'Content-Type': 'application/JSON'} ;
http.Response responseTlk = await http.post(test_URL, headers: headers, body: tikkieBody) ; // payload);
print(responseTlk.statusCode); // 201
statusCode = responseTlk.statusCode ;
respValues = responseTlk.body.toString() ;
print("respValues: $respValues") ;
jsonData = json.decode(responseTlk.body) as Map;
if (statusCode == 201) {
TikkieURL = jsonData['url'] ;
print("201 tikkie url: $TikkieURL") ;
} else {
p = respValues.indexOf('"message": "', 1) ;
e = respValues.indexOf('",', p+12) ;
TikkieError = respValues.substring(p+12, e) ;
status = statusCode.toString() ;
print(" Error Statuscode $status, message: $TikkieError") ;
print(" Test stopt hier ...") ;
}
print("\r\nEinde Tikkie test") ;
}
Hieronder het resultaat. Voor de duidelijkheid zijn er extra lege regels in de uitvoer geplaatst.
D:\Ontwikkelomgeving\Dart\tikkie>dart main.dart
Start testing the Tikkie https post request:
body: { "amountInCents": 500, "description": "Sandbox test Tikkie", "expiryDate": "2020-10-31", "referenceId": "Test.factuur.nr" }
201
respValues: {"appToken":"da96d1a2-f2a5-4ada-bdc9-5156f1570f75"}
201 app-token: da96d1a2-f2a5-4ada-bdc9-5156f1570f75
201
respValues: {"paymentRequestToken":"ezwhvjb8EF6hGwBn4C4BAy","amountInCents":500,"referenceId":"Test.factuur.nr","description":"Sandbox test Tikkie","url":"https://sbx.tikkie.me/pay/TSTCSACkyLx/ezwhvjb8EF6hGwBn4C4BAy","expiryDate":"2020-10-31","createdDateTime":"2020-10-17T14:35:48.463Z","status":"OPEN","numberOfPayments":0,"totalAmountPaidInCents":0}
201 tikkie url: https://sbx.tikkie.me/pay/TSTCSACkyLx/ezwhvjb8EF6hGwBn4C4BAy
Einde Tikkie test
D:\Ontwikkelomgeving\Dart\tikkie>
Prototype getTikkie gemaakt met B4J Windows
Bij toeval kwam ik bij een internet zoektocht het al langer bestaande ontwikkelplatform B4X.com tegen.
Hiermee kun je redelijk snel Android (B4A), Windows (B4J), Linux en iOS (B4i) apps en applicaties ontwikkelen. Na het maken van een eenvoudige Android app om mijn IOT
domotica Plugwise en Zwave lampen aan/uit te kunnen schakelen, leek het me wel aardig om als oefenproject een eenvoudig tooltje te bouwen waarmee je
een Tikkie payment request aan kunt maken. En waarbij de Tikkie URL link via een clipboard copy-paste actie in een mail of Word factuur document
kan worden geplakt.
Na een vlotte start bleek het aanroepen van de Tikkie webservice toch iets lastiger te zijn dan ingeschat ondanks de hulp van ruim beschikbare tutorials
en het zeer actieve forum.
Maar naar mate je meer ervaring met dit tool op doet, is de klus toch binnen een uur of acht geklaard.
De meeste tijd werd besteed aan het uitzoeken van wat mogelijk is. Het programmeren gaat op basis van een BASIC taalvariant,
en afhankelijk van je doel platform wordt de runnable code omgezet in Java.
De app bestaat uit 2 programma delen, te weten het main deel en de class module Tikkie.bas.
De laatste module bevat de code om de Tikkie webservice aan te roepen. het main deel bevat als een Const variabele de webservice URL's en de
API-KEY en APP-TOKEN.
Bij het testen met de sandbox vragen we eerst met de test-Api-Key het app-token op en vervolgens de Tikkie URL.
Verander wel in het main deel de naam MY_API_KEY in de juiste Api-Key, die je eerder hebt aangevraagd. En natuurlijk ook MY_APP_TOKEN.
Hieronder volgt de bron code van de Tikkie.bas class module.
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=8.9
@EndOfDesignText@
Sub Class_Globals
Public AppToken, TikkieError, TikkieURL, MsgText, CreationDate, PayReqToken As String
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize (pTikkieURL As String, pTikkieAppToken As String)
TikkieURL = pTikkieURL
AppToken = pTikkieAppToken
MsgText = "initialized"
CreationDate = "jaar-mm-dd"
PayReqToken = ""
TikkieError = ""
End Sub
Public Sub GetAppToken As String
Return AppToken
End Sub
Public Sub GetTikkieURL As String
Return TikkieURL
End Sub
Public Sub GetMsgText As String
Return MsgText
End Sub
Public Sub GetCreationDate As String
Return CreationDate
End Sub
Public Sub GetPayReqToken As String
Return PayReqToken
End Sub
Sub GetABNATikkieToken(pURL As String, pTikkieApiKey As String) As ResumableSub
' eerst tikkie app token verkrijgen met Post
Dim PostResponse As String
Dim PostStatus, p, e As Int
Dim tikkieResult As Boolean = False
Dim DQ As String = Chr(34)
Dim HTab As String = Chr(9)
Dim LF As String = Chr(10)
Dim j As HttpJob
j.Initialize("", Me)
' j.Download(pURL)
j.PostString(pURL, "")
j.GetRequest.SetHeader("API-Key", pTikkieApiKey)
Wait For (j) JobDone (j As HttpJob)
PostStatus = j.Response.StatusCode
If j.Success Then
PostResponse = j.GetString
If PostStatus = 201 Then
p = PostResponse.IndexOf(DQ & "appToken" & DQ & ":" &DQ)
e = PostResponse.IndexOf2(DQ & "}", p)
AppToken = PostResponse.SubString2(p+12,e)
TikkieURL = "alleen token test"
MsgText = "succes"
tikkieResult = True
Else
TikkieURL = "alleen token test error status" & PostStatus
PostResponse = PostResponse.Replace(HTab, "")
PostResponse = PostResponse.Replace(LF, "")
PostResponse = PostResponse.Replace(" ", " ")
p = PostResponse.IndexOf(DQ & "errors" & DQ & ": [")
e = PostResponse.IndexOf2("]", p+11)
TikkieError = PostResponse.SubString2(p+11, e)
p = TikkieError.IndexOf(DQ & "message" & DQ & ": " & DQ)
e = TikkieError.IndexOf2(DQ & ",", p+12)
TikkieError = TikkieError.SubString2(p+12, e)
tikkieResult= False
End If
Else
TikkieURL = "test also error status " & PostStatus
TikkieError = ""
PostResponse = j.ErrorMessage
PostResponse = PostResponse.Replace(HTab, "")
PostResponse = PostResponse.Replace(LF, "")
PostResponse = PostResponse.Replace(" ", " ")
p = PostResponse.IndexOf(DQ & "errors" & DQ & ": [")
e = PostResponse.IndexOf2("]", p+11)
TikkieError = PostResponse.SubString2(p+11, e)
p = TikkieError.IndexOf(DQ & "message" & DQ & ": " & DQ)
e = TikkieError.IndexOf2(DQ & ",", p+12)
TikkieError = TikkieError.SubString2(p+12, e)
MsgText = TikkieError
tikkieResult= False
End If
j.Release
Return tikkieResult
End Sub ' GetABNATikkieToken
Sub GetABNATikkieLink(pURL As String, pTikkieApiKey As String, pTikkieAppToken As String, pTikkieAmtCnts As String, pTikkieDescr As String, pTikkieExpDate As String, pTikkieRefID As String) As ResumableSub
Dim tikkieResult As Boolean = False
Dim TikkieJSON As String
Dim PostResponse As String
Dim PostStatus, p, e As Int
Dim DQ As String = Chr(34)
Dim DQs As String = "{" & DQ
Dim DQv As String = DQ & ":" & DQ
Dim DQi As String = DQ & ":"
Dim DQiw As String = "," & DQ
Dim DQw As String = DQ & "," & DQ
Dim DQe As String = DQ & "}"
TikkieJSON = DQs & "amountInCents" & DQi & pTikkieAmtCnts & DQiw & "description" & DQv & pTikkieDescr & DQw
TikkieJSON = TikkieJSON & "expiryDate" & DQv & pTikkieExpDate & DQw & "referenceId" & DQv & pTikkieRefID & DQe
Dim j As HttpJob
j.Initialize("", Me)
j.PostString(pURL, TikkieJSON)
j.GetRequest.SetContentType("application/JSON")
j.GetRequest.SetHeader("API-Key", pTikkieApiKey)
j.GetRequest.SetHeader("X-App-Token", pTikkieAppToken)
Wait For (j) JobDone (j As HttpJob)
PostStatus = j.Response.StatusCode
If j.Success Then
PostResponse = j.GetString
If PostStatus = 201 Then
p = PostResponse.IndexOf(DQ & "paymentRequestToken" & DQv)
e = PostResponse.IndexOf2(DQ & "," & DQ, p+22)
PayReqToken = PostResponse.SubString2(p+23, e)
p = PostResponse.IndexOf(DQ & "url" & DQv)
e = PostResponse.IndexOf2(DQ & "," & DQ, p+7)
TikkieURL = PostResponse.SubString2(p+7, e)
p = PostResponse.IndexOf(DQ & "createdDateTime" & DQv)
e = PostResponse.IndexOf2(DQ & "," & DQ, p+19)
CreationDate = PostResponse.SubString2(p+19, e)
MsgText = "succes: aangemaakt op " & CreationDate
tikkieResult = True
Else
TikkieURL = "alleen tikkie URL test error"
MsgText = "error" & j.ErrorMessage
tikkieResult= False
End If
Else
TikkieURL = "also error"
MsgText = "error" & j.ErrorMessage
CreationDate = "jaar-mm-dd"
PayReqToken = ""
tikkieResult= False
End If
j.Release
Return tikkieResult
End Sub ' GetABNATikkieLink
Hieronder het getTikkie start window, na het invullen van de gegevens klik op de button Go Get om de Tikkie URL op te halen.

Je ziet in het plaatje tevens een deel van het B4J IDE platform.
Het resultaat zien we in het volgende plaatje.

De ZIP file met de code van de app kun je voor eigen gebruik downloaden met deze link app_getTikkie.zip. De ZIP file bevat een map getTikkie met de bestanden getTikkie.b4j, getTikkie.b4j.meta, Tikkie.bas en een submap Files met het layout bestand Layout1.bjl.
Voordat je het gaat gebruiken, en je hebt nog geen of weinig ervaring met het B4J IDE platform, verdiep je dan eerst in het gebruik ervan en hoe je
een ontwikkel project opzet.
Zelf gebruik de app als ik een maatwerk factuur moet maken en er een Tikkie link wil bijvoegen.
Samenvatting
Het doel van dit artikel is om mijn ervaringen met het Tikkie API portal te delen met anderen, zodat zij van de door mij opgedane kennis en ervaring gebruik kunnen maken. En tevens om deze kennis voor mijzelf vast te leggen voor eventueel later gebruik.
Ervaringen van de afgelopen maanden: sinds 10 oktober van 2020 zijn tot nu toe (4 maart 2021) een 155 facturen met een Tikkie URL verstuurd, hiervan zijn 67 facturen met een Tikkie betaald, of te wel een 42%.
Commentaar of tips
Uw commentaar of tips voor verbeteringen worden op prijs gesteld, u kunt ze sturen naar het emailadres
pr@jwteunisse.nl
Auteursrechten (copyright)
© Auteursrechten & Copyright 2020-2021 by J.W. Teunisse
De in dit artikel beschreven software code en subroutines mogen worden hergebruikt, echter voor de gebruikers eigen verantwoordelijkheid.
De auteur is niet aansprakelijk voor eventuele schade door het toepassen van de in dit artikel beschreven programma code.
This piece of software as presented in this article is provided 'as-is', without any express or implied warranty.
In no event will the author be held liable for any damages arising from the use of this article or software code.