Commerce Platform integration - step by step

Introduction

Security is of utmost importance when processing customer and payment data. This is why the PAYONE Commerce Platform is build in such a way that allows secure processing.  

The API Key and API Secret provide a unique signature to protect incoming and outgoing requests  between your system and our platform. The API Key and API Secret can be retrieved and managed in the Commerce Portal. Any server-to-server request from your system to our platform needs to contain API Key and should be hashed using the API Secret. The platform will authenticate the request by checking the API Key and the signature. 

In this guide we explain the necessary steps in order to securely integrate the PAYONE Commerce Platform.

---end

API Key and api Secret configuration

The API Key and API Secret are required for a successful authentication of the request. The key pair can be retrieved, updated, and deleted in the PAYONE Commerce Portal under Configuration > API Configuration:

Primary

This label indicates the primary key pair that should be used. The primary key pair will be used for webhooks. 

Primary keys cannot be deactivated. For this purpose a different key pair as to be selected as "Primary" first.

---end

Expiration Date

Indicates the date and time when a key pair will expire. Per default a key pair is valid for two years.

Status Indicates whether a key pair is Active or Inactive (expired / deactivated)

---end

Make sure that only authorized employees with the required role have access to the API Configuration. In case of security breaches you should deactivate the existing key pair and create a new one.

---end

Server to server authentication

The API credentials should be used to send requests directly against the endpoints of the Commerce Platform. This requires the following authentication mechanism:

  1. HTTP headers must be hashed using your API Secret (also called signature contents or signed-data)
  2. This hash ist called signature and will be used together with your API Key to verify the authentication

    For a better understanding of the authentication there will be GET, POST and DELETE examples in the following chapters. 

---end

General authentication process

A successful GET/POST/PATCH/DELETE request is a three step approach:

  • 1. Create a string-to-hash, consisting of several HTTP headers
  • 2. Calculate the signature using the algorithm HMAC-SHA256 with your API Secret
  • 3. Send the actual request to our platform, including the headers, the signature and your API Key

---end

1. Creating a string-to-hash

Compose the string-to-hash (the signature contents or signed-data) by following these rules:

Order of the header: The string-to-hash consists of the following headers, listed in a specific order:

<HTTP method>
<Content-Type>
<Date>
<CanonicalizedHeaders>
<CanonicalizedResource>
Header Content Conventions

Each header is constructed in a specific way:

<lowercased header name>:<header value>;

Apply the following conventions for the content of the headers:

Headers Conventions
<HTTP Method>

Use either "GET", "POST", "PATCH" or "DELETE" (always in uppercase) depending on the desired HTTP method used.

<Content-Type>

Use the fixed value "application/json; charset=utf-8"  for POST and PATCH requests and use an empty string for GET or DELETE requests.

<Date>

Use the "Date" header in RFC1123 format.

<CanonicalizedHeaders>

Include custom headers specific to your application, these headers always start with X-GCS, such as X-GCS-ClientMetaInfo

<CanonicalizedResource>

Include the target URI without the HTTP method, and include all decoded query parameters.

---end

Unwrap Header Values

If a header value is wrapped over multiple lines, unwrap each line as described here. Unwrap by replacing a new line or multiple spaces by a single white space. Example:

A very long line<CR><LF>
<SPACE><SPACE><SPACE><SPACE>that does not fit on a single line 

will become

A very long line that does not fit on a single line
Trim Whitespaces

Trim all whitespaces which exist at the start and end of the value. Example:

<space><space><space>Some text<space><space><space>

will become

some text
Make sure that each <item> is followed by a new line (line feed), including the last item.

POST Create commerce case example
public String createDateTime() {
    Calendar calendar = Calendar.getInstance();
    SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
    dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    return dateFormat.format(calendar.getTime());
}

public String createStringToHash(String dateTime) {
    String merchantId = "YourMerchantId";
    String requestMethod = "POST";
    String contentType = ContentType.APPLICATION_JSON.getMimeType();
    String uriResource = "/commerce-cases";

    String endpointURL = "/v1/" + merchantId + uriResource;

    return requestMethod + "\n" + contentType + "\n" + dateTime + "\n" + endpointURL + "\n";
}

Although the same rules apply for either HTTP method, you need to take the differences for GET/DELETE/PATCH requests into account when creating the string-to-hash (i.e. missing content type, addition of query parameters to the request URI).

Have a look at the GET example or the DELETE example in the next chapters for instructions

---end

2. Calculate signature

Once you have created the string-to-hash (the signature contents or signed-data), you need to hash it via the MAC algorithm HMAC-SHA256 with your API Secret.

The result of the hashing is the signature you need to provide in the authorization header of your request.

public static String createEncodedSignature(String stringToHash, String yourApiSecret) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
    // Convert stringToHash + key into byte array
    String algorithm = "HmacSHA256";
    byte[] keyAsBytes = yourApiSecret.getBytes("UTF-8");
    byte[] stringToHashAsBytes = stringToHash.getBytes("UTF-8");

    SecretKeySpec secretKeySpec = new SecretKeySpec(keyAsBytes, algorithm);
    Mac mac = Mac.getInstance(algorithm);
    mac.init(secretKeySpec);
    byte[] hash = mac.doFinal(stringToHashAsBytes);
    return Base64.encodeBase64String(hash);
}

---end

3. Send request

---end

Finally, you can send your request, including:

  1. The headers you used for creating the string-to-hash (the signature contents or signed-data)
  2. The base64-encoded hash result (the signature) and your API Key in the HTTP header 'Authorization' according to this format:

Authorization = "GCS v1HMAC:" + API Key + ":" + signature

Authorization = "GCS v1HMAC:" + API Key + ":" + signature

The JSON-encoded request body for POST and PATCH requests (GET/DELETE do not require a request body):

public static String sendRequest(String apiKey, String encodedSignature, String merchantId, String uriResource, String requestMethod, String contentType, String dateTime) throws IOException {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    String authorizationHeaderValue = "GCS v1HMAC:" + apiKey + ":" + encodedSignature;

    String url = "https://preprod.commerce-platform.payone.com/v1/" + pspid + uriResource;

    if ("POST".equals(requestMethod)) {

        HttpPost httpPost = new HttpPost(url);

        // Build and send POST request
        String requestBodyAsString = "{"
                                   + "  'merchantReference': 'commerce-case-123',"
                                   + "  'customer': {"
                                   + "    'merchantCustomerId': 'customer-1234'"
                                   + "  },"
                                   + "  'checkout': {"
                                   + "    'amountOfMoney': {"
                                   + "      'amount': 1000,"
                                   + "      'currencyCode': 'EUR'"
                                   + "    },"
                                   + "    'references': {"
                                   + "      'merchantReference': 'customer-order-abc'"
                                   + "    }"
                                   + "  }"
                                   + "}";

        HttpEntity httpEntity = new StringEntity(requestBodyAsString, ContentType.APPLICATION_JSON);
        addHeaders(httpPost, dateTime, contentType, authorizationHeaderValue);
        httpPost.setEntity(httpEntity);

        // Execute
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);

        // Parse JSON response
        return parseResponse(httpResponse);

    } else if ("PATCH".equals(requestMethod)) {

        HttpPatch httpPatch = new HttpPatch(url);

        // Build and send PATCH request
        String requestBodyAsString = "{"
                                   + "  'personalInformation': {"
                                   + "    'name': {"
                                   + "      'firstName': 'Ethan',"
                                   + "      'surname': 'Johnson'"
                                   + "    }"
                                   + "  }"
                                   + "}";

        HttpEntity httpEntity = new StringEntity(requestBodyAsString, ContentType.APPLICATION_JSON);
        addHeaders(httpPatch, dateTime, contentType, authorizationHeaderValue);
        httpPatch.setEntity(httpEntity);

        // Execute
        CloseableHttpResponse httpResponse = httpClient.execute(httpPatch);

        // Parse JSON response
        return parseResponse(httpResponse);

    } else if ("GET".equals(requestMethod)) {
        HttpGet httpGet = new HttpGet(url);
        addHeaders(httpGet, dateTime, contentType, authorizationHeaderValue);

        CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
        return parseResponse(httpResponse);
    } else if ("DELETE".equals(requestMethod)) {
        HttpDelete httpDelete = new HttpDelete(url);
        addHeaders(httpDelete, dateTime, contentType, authorizationHeaderValue);

        CloseableHttpResponse httpResponse = httpClient.execute(httpDelete);
        return parseResponse(httpResponse);
    } else {
        return "Something went wrong";
    }
}

private static void addHeaders(HttpUriRequest httpUriRequest, String dateTime, String contentType, String authorizationHeaderValue) {
    httpUriRequest.addHeader(AUTHORIZATION, authorizationHeaderValue);
    httpUriRequest.addHeader(DATE, dateTime);
    httpUriRequest.addHeader(CONTENT_TYPE, contentType);
}

private static String parseResponse(CloseableHttpResponse httpResponse) throws IOException {
    String responseString = EntityUtils.toString(httpResponse.getEntity());
    JSONObject response = new JSONObject(responseString);
    return response.toString();
}

---end

Additonal examples for string to hash

As the general requirements for a GET/POST/DELETE/PATCH request differ, the construction of the string-to-hash does as well.
Have a look at the following examples to learn how to implement them in your system

---end

POST Request

The string-to-hash (the signature contents or signed-data) for a CommerceCase request:

POST
application/json; charset=utf-8
Wed, 02 Mar 2023 11:15:51 GMT
/v1/yourMerchantId/commerce-cases
Be aware that there is a new line (line feed) after the last header (URI resource) which you also need to consider when hashing the string

---end

GET Request

The string-to-hash (the signature contents or signed-data) for a GetCheckout request:

GET
application/json; charset=utf-8
Wed, 02 Mar 2023 11:15:51 GMT
/v1/yourMerchantId/commerce-cases/yourCommerceCaseId/checkouts/yourCheckoutId
Be aware that there is an empty string for Content-Type header, as GET request do not have a request body. There is a new line (line feed) after the last header (URI resource) which you also need to consider when hashing the string.

---end

DELETE Request

The string-to-hash (the signature contents or signed-data) for a DeleteCheckout request:

DELETE
application/json; charset=utf-8
Wed, 02 Mar 2022 11:15:51 GMT
/v1/yourMerchantId/commerce-cases/yourCommerceCaseId/checkouts/yourCheckoutId
Be aware that there is an empty string for Content-Type header, as GET request do not have a request body. There is a new line (line feed) after the last header (URI resource) which you also need to consider when hashing the string.

---end

Patch Request

The string-to-hash (the signature contents or signed-data) for a PatchCheckout request:

PATCH
application/json; charset=utf-8
Wed, 02 Mar 2022 11:15:51 GMT
/v1/yourMerchantId/commerce-cases/yourCommerceCaseId/checkouts/yourCheckoutId
Be aware that there is a new line (line feed) after the last header (URI resource) which you also need to consider when hashing the string

---end