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:

  1. het Tikkie portaal: Tikkie Me en vraag een Tikkie voor bedrijven account aan;
  2. 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.

voorbeeld Tikkie 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" }'

Back to top

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.

Back to top

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>

Back to top

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>

Back to top

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.

voorbeeld getTikkie betaling

Je ziet in het plaatje tevens een deel van het B4J IDE platform.

Het resultaat zien we in het volgende plaatje.

resultaat getTikkie betaling

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.

Back to top

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%.

Back to top

Commentaar of tips

Uw commentaar of tips voor verbeteringen worden op prijs gesteld, u kunt ze sturen naar het emailadres pr@jwteunisse.nl

Back to top

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.