How to Integrate Klarna with PHP

Once again we delve into the world of confusing documentation with broken links, no clear explanation of what you need to do and a lack of cohesion between the different parts of the system so you’re left piecing it together yourself.

As always, the client is told by the company that integration is easy so they’re expecting a low quote, and on first inspection all looks good; plenty of documentation and a PHP SDK! Fantastic. Then you find out the SDK was deprecated a few years ago and the new documentation doesn’t tell you where to start or give any step-by-step instruction. I couldn’t find any examples on the web so, after working it out myself, have written this blog post so you don’t have to.

Our client has a fully fledged and bespoke checkout system so all we needed from Klarna was the option for customers to pay via them at the end of the checkout and for the result to be passed back. A pretty straightforward, and I would have thought common, scenario. One problem was that when contacting Klarna with an issue it felt like I was the first person to ever use their API; because I was integrating manually (rather than, I guess, plugging in to Shopify or whatever) the question had to be escalated to their technical team then I had to wait for them to email back so you end up not contacting them at all because it’s such a faff. Unrelated to the API I also experienced issues getting my account linked to both of my client’s; emails never arrived; Klarna said I shouldn’t have access even though I did to one of them; they couldn’t explain it. All of this takes so much time.

API Credentials

Before you start with the code you’ll need to get some API Credentials.

You can get test credentials by creating an account in the Playground (https://app.playground.klarna.com/login, then create credentials here https://portal.playground.klarna.com/settings/api-credentials).

You’ll find live credentials, or be able to generate new ones if needed, here https://portal.klarna.com/settings/api-credentials.

Epiphany

After a lot of reading I realised there were 3 main steps in the Klarna process:

  1. Create session
  2. Create, er, another session
  3. Check status of order

I wrote a class with the functions as can be found below.

Code

Set up the class, entering your own website URL and Klarna API credentials. Comment/uncomment the relevant lines when testing or going live.

class Klarna{

    var $site_url = "https://www.example.com";

    // Live
    /*var $base_url = "https://api.klarna.com";
    var $uid = "";
    var $pass = "";*/

    // Test
    var $base_url = "https://api.playground.klarna.com";
    var $uid = "";
    var $pass = "";

    function __construct(){
    }
}

The following describes the three steps split into three methods/functions which need to be added to the class.

1. Create Session

https://docs.klarna.com/hosted-payment-page/get-started/accept-klarna-payments-using-hosted-payment-page/#step-by-step-integration-1-create-kp-session-with-the-payments-api

We pass the customer’s $order details through to this function as we need to send them to Klarna. The way you store your data will be different but you just need to loop through the order’s products adding name, price, image etc to an array, which we then include in another array detailing the customer’s name and address.

Then cURL this data to Klarna which, when successful, returns a “session_id”.

Note: I tried entering various amounts in “total_tax_amount”, getting an error each time, so eventually gave up and set it to zero.

function createSession($order){
    $url = "{$this->base_url}/payments/v1/sessions";

    // Build order_lines
    $contents = $order->cart;
    foreach($contents as $line){
        $product_price = $line->product->grossprice * 100;

        $order_line = array(
            "image_url" => "{$this->site_url}/images/250/250/{$line->product->main_image}",
            "type" => "physical",
            "reference" => $line->product->gtin,
            "name" => $line->product->name,
            "quantity" => $line->quantity,
            "unit_price" => $product_price,
            "tax_rate" => 20,
            "total_amount" => $product_price * $line->quantity,
            "total_tax_amount" => 0
        );

        $order_lines[] = $order_line;
    }

    $data = array(
        "purchase_country" => "gb",
        "purchase_currency" => "gbp",
        "locale" => "en-GB",
        "order_amount" => $order->totalgross * 100,
        "order_lines" => $order_lines,
        "billing_address" => array(
            "given_name" => $order->firstname,
            "family_name" => $order->lastname,
            "email" => $order->email,
            "street_address" => $order->addr1,
            "street_address2" => $order->addr2,
            "postal_code" => $order->postcode,
            "city" => $order->city,
            "region" => $order->county,
            "phone" => $order->telephone,
            "country" => $order->country
        )
    );

    $payload = json_encode($data);

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Content-Type: application/json",
        "Cache-Control: no-cache",
        "Authorization: Basic ".base64_encode("{$this->uid}:{$this->pass}")
    ));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

    $response = curl_exec($ch);
    curl_close($ch);

    // Process the response
    if($response === false){
        // Request failed
        echo "cURL error: " . curl_error($ch) . "<br/>";
        echo "cURL error code: " . curl_errno($ch);
    }else{
        // Request succeeded
        $result = json_decode($response, true);

        return $result['session_id'];
    }
}

2. Create HPP Session

https://docs.klarna.com/hosted-payment-page/api-documentation/create-session/

Using the “session_id” we obtained from createSession() we now need to create a Hosted Payment Page session. This generates a URL to which we redirect the customer so they can pay on Klarna’s website.

We also pass our own $orderid in to use in the return URLs.

Note: The documentation says “token=<random_uuid>” but I wasn’t able to find out what the random UUID was suppose to be for, so I used it to contain our system’s order ID so we can reconcile the order with the result on our return URL. The “{{session_id}}” is automatically filled by Klarna, so don’t change that. I’m not sure whether it’s the same “session_id” that’s generated by createSession(); if it is then you could save that with the order in your system and reconcile that way, but using the token worked fine for me. “{{order_id}}” is automatically filled with Klarna’s own order ID.

function createHPPSession($session_id, $orderid){
    $url = "{$this->base_url}/hpp/v1/sessions";

    $data = array(
        "payment_session_url" => "{$this->base_url}/payments/v1/sessions/$session_id",
        "merchant_urls" => array(
            "success" => "{$this->site_url}/shop/thank-you.html?token={$orderid}&sid={{session_id}}&order_id={{order_id}}",
            "cancel" => "{$this->site_url}/shop/display-basket.html",
            "back" => "{$this->site_url}/shop/display-basket.html",
            "failure" => "{$this->site_url}/shop/thank-you.html?token={$orderid}&sid={{session_id}}",
            "error" => "{$this->site_url}/shop/thank-you.html?token={$orderid}&sid={{session_id}}"
        ),
        "options" => array(
            "place_order_mode" => "PLACE_ORDER"
        )
    );

    $payload = json_encode($data);

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Content-Type: application/json",
        "Cache-Control: no-cache",
        "Authorization: Basic ".base64_encode("{$this->uid}:{$this->pass}")
    ));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

    $response = curl_exec($ch);
    curl_close($ch);

    // Process the response
    if($response === false){
        // Request failed
        echo "cURL error: " . curl_error($ch) . "<br/>";
        echo "cURL error code: " . curl_errno($ch);
    }else{
        // Request succeeded
        $result = json_decode($response, true);

        if($result['redirect_url']){
            header("Location: {$result['redirect_url']}");
            die;
        }
    }
}

3. Check Order Status

https://docs.klarna.com/order-management/manage-orders-with-the-api/view-and-change-orders/check-the-details-of-an-order/

On the return URL (“/shop/thank-you.html” in our case), where the user is redirected after Klarna, we need to check the status of the order; we call the below function which passes Klarna’s order ID back to Klarna and returns information about the order, including its status.

function checkOrderStatus($orderid){
    $url = "{$this->base_url}/ordermanagement/v1/orders/$orderid";

    $ch = curl_init($url);

    // Set options
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Content-Type: application/json",
        "Cache-Control: no-cache",
        "Authorization: Basic ".base64_encode("{$this->uid}:{$this->pass}")
    ));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPGET, true); // Use GET method

    // Execute the request
    $response = curl_exec($ch);
    curl_close($ch);

    // Check for errors
    if($response === false){
        // Error handling
        echo "Error occurred while fetching the data: " . curl_error($ch);
    }else{
        // Process the response

        return json_decode($response, true);
    }
}

Code Usage

That’s all the code that deals with Klarna, neatly packaged up into a class, and this is how we use it: when the customer chooses to pay by Klarna the code we run is simple:

$klarna = new Klarna();
$session_id = $klarna->createSession($order);
$klarna->createHPPSession($session_id, $orderid); // Klarna's $session_id, our $orderid

This redirects the customer to Klarna.

On the return page of the website we check the order with this code:

$token = get("token"); // Our order ID
$order_id = get("order_id"); // Klarna's order ID

$klarna = new Klarna();
$info = $klarna->checkOrderStatus($order_id);

// Check status of response
if($info['status'] == "AUTHORIZED"){
    // Success
}else{
    // Fail
}

If $info['status'] == "AUTHORIZED" then the order has been successful so we display a success message and carry out our usual process of fulfilling an order i.e. marking it as such in our database and sending the customer an email. Otherwise the order has not been successful so we display a relevant message to the customer.

You’ll probably want to add some error handling but that’s it! Simple when you know how.

This code is free to use at your own discretion. It comes without warranty. Please feel free to feedback any edits.


We'd love to hear from you!

If you think Bronco has the skills to take your business forward then what are you waiting for?

Get in Touch Today!

Discussion

Write a comment...
  • BudyDev

    It works great. Thank you very much for sharing 🙂

  • devteam

    Thanks for this, it helped a lot!

Add a Comment