PAYONE API HMAC authentication in AL
This is one of the more specific posts about API authentication in Business Central. But probably someone could use the snippets for his own implementation. Right now we’re implementing the payment API of PAYONE for one of our customers. This provider needs the API request headers and additional information to be hashed using HMAC256 and send with every request.
So, actually this a specific HowTo on the PAYONE API authentication.
This code here has not yet been optimized, generalized and simplified! It is more or less hardcoded and the intention is just to get you an idea how to approach such an authentication process.
Authentication steps
- Create a string-to-hash, consisting of several HTTP headers
- Calculate the hash using the algorithm HMAC-SHA256 with your API Secret
- Send the actual request, including the headers, the hash and your API Key
Create the String
With this function we create the string to be hashed. It could consist of every information you want.
The lines are separated using a line feed.
local procedure CreateStringToHash(pPSPId: Text; pUriResource: Text; ApiIdentifier: Text; pRestMethods: Enum "Http Request Type"; pFormattedDateTime: Text): Text
var
stringToHash: Text;
contentType: Text;
endpointURL: Text;
requestMethod: Text;
LF: char;
begin
LF := 10;
requestMethod := Format(pRestMethods);
if requestMethod = 'POST' then
contentType := 'application/json';
if CopyStr(pUriResource, 1, 1) <> '/' then
pUriResource := '/' + pUriResource;
endpointURL := '/v2/' + pPSPId + pUriResource;
if ApiIdentifier <> '' then
endpointURL += '/' + ApiIdentifier;
stringToHash := requestMethod + LF + contentType + LF + pFormattedDateTime + LF + endpointURL + LF;
exit(stringToHash);
end;
Hashing the String
With this method we hash the string using our API secret using the standard Cryptography Management codeunit.
local procedure CreateAuthenticationHash(StringToHash: Text; ApiSecret: Text): Text
var
CryptoMgt: Codeunit "Cryptography Management";
HashAlgorithmType: Option HMACMD5,HMACSHA1,HMACSHA256,HMACSHA384,HMACSHA512;
begin
exit(CryptoMgt.GenerateHashAsBase64String(StringToHash, ApiSecret, HashAlgorithmType::HMACSHA256));
end;
Request
Using these procedures we could now create an API request:
PSPId := '';
ApiKey := '';
ApiSecret := '';
lDateTime := CurrentDateTime;
FormattedDatetime := CreateFormattedDateTime(lDateTime);
StringToHash := CreateStringToHash(PSPId, '/hostedcheckouts', Enum::"Http Request Type"::POST, FormattedDatetime);
AuthorizationHash := CreateAuthenticationHash(StringToHash, ApiSecret);
AuthorizationHeader := StrSubstNo('GCS v1HMAC:%1:%2', lApiKey, AuthorizationHash);
HttpRequestMessage.Method := 'POST';
HttpRequestMessage.SetRequestUri(StrSubstNo('https://payment.preprod.payone.com/v2/%1/hostedcheckouts', PSPId);
HttpRequestMessage.GetHeaders(HttpHeaders);
HttpHeaders.Add('Date', FormattedDatetime);
HttpHeaders.Add('Authorization', AuthorizationHeader);
if HttpRequestMessage.Method = 'POST' then begin
HttpContent.WriteFrom('{ "key": "value" }');
HttpContent.GetHeaders(HttpHeaders);
if HttpHeaders.Contains('Content-Type') then
HttpHeaders.Remove('Content-Type');
HttpHeaders.Add('Content-Type', 'application/json');
HttpRequestMessage.Content := HttpContent;
end;
HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
Helper: Formatting date
This API needs a specific format in the “date” header, so we create a helper function to do that. As we are situated in the CET timezone and don’t want to mess around with day and month translations, we do some magic here as well. The result should look like:
Wed, 02 Mar 2024 11:15:51 GMT
local procedure CreateFormattedDateTime(pDatetime: DateTime): Text
var
FormattedDatetime: Text;
LanguageID: Integer;
begin
LanguageID := GlobalLanguage;
GLOBALLANGUAGE := 1033;
FormattedDatetime := FORMAT(pDateTime - 3600000, 0, '<Weekday Text,3>, <Day,2> <Month Text,3> <Year4> <Hour,2>:<Minute,2>:<Second,2> GMT');
GLOBALLANGUAGE := LanguageID;
exit(FormattedDatetime);
end;
As already mentioned this could now be wrapped, simplified and used for all API calls.