Tax & Shipping with the Klarna Checkout

The business logic for both tax calculations and the selection of shipping options is specific to each merchant: 

  • Sales tax calculation may depend on (among other things) the shipping address of the customer.
  • Shipping options may depend on the customer's shipping address.

Options

Klarna Checkout allows the merchant to:

  1. Update the tax amount of the order when the customer enters or changes their shipping address.

  2. Present their shipping options within the Klarna Checkout iframe

  3. Update the shipping options when the customer enters or changes their shipping address.

If the customer only enters one address in KCO it will serve as both billing and shipping address.

Flow

Process

The process of updating tax and shipping consists of three phases:

  1. Initialization

  2. Callback to merchant

  3. Order creation

Initialization

  • In order to update the cart when shipping address changes, you need to add a callback url when initializing the checkout.

  • If you want to display shipping options from start you can provide an initial list of shipping options.

Callback

When the shipping address is changed a POST request is sent to the callback url specified by the merchant.

The body of the request contains:

  • shipping_address

  • order_amount

  • order_tax_amount

  • order_lines

The order_amount, order_tax_amount and order_lines make up the cart object.

The merchant responds to the request. As a merchant you can choose to update:

  • The cart
  • Shipping options
  • Both the cart and the shipping options
  • None of them.

The response is handled:

  1. If a cart object is present the order will be updated with the provided cart

  2. If a list of shippingoptions is present the order will be updated with the provided shipping options.

  3. The purchase will not proceed in any of these cases:

    • the cart object is invalid

    • The shipping options are invalid

    • the response is a status code other than 200

    • There is no response within 10 seconds

If so, a message is displayed to the customer telling them that something went wrong and that they can try again. This will be logged for merchant support.

Order creation

  1. The customer selects a shipping option.
  2. The shipping option is added to the order as selected_shipping_option.
  3. If the selected shipping option has a price, an order line is added to the order:
  • type - shipping_fee
  • name - name of the shipping option
  • Price and tax details are copied from the shipping option

Updating the tax amount and shipping cost

If you provide the address_update URL as part of the merchant_urls a POST request will be sent to that URL when the customer enters or edits the shipping address. 

The body of the POST request will contain these fields:

  • order_amount
  • order_tax_amount
  • order_lines
  • billing_address
  • shipping_address
  • selected_shipping_option

Example of the POST request:

{
  "order_amount": 1599,
  "order_tax_amount": 0,
  "order_lines": [
    {
      "type": "physical",
      "reference": "fH5aK96A",
      "name": "Blue T-Shirt",
      "quantity": 1,
      "unit_price": 1599,
      "tax_rate": 0,
      "total_amount": 1599,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "shipping_fee",
      "name": "Free Shipping",
      "quantity": 1,
      "unit_price": 0,
      "tax_rate": 0,
      "total_amount": 0,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "sales_tax",
      "reference": "Sales Tax",
      "name": "Sales Tax",
      "quantity": 1,
      "unit_price": 0,
      "tax_rate": 0,
      "total_amount": 0,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    }
  ],
  "billing_address": {
    "given_name": "Klara",
    "family_name": "Joyce",
    "email": "klara.joyce@klarna.com",
    "street_address": "1 Safeway",
    "postal_code": "12345",
    "city": "Knoxville",
    "region": "TN",
    "phone": "+1 555-555-5555",
    "country": "us"
  },
  "shipping_address": {
    "given_name": "Klara",
    "family_name": "Joyce",
    "email": "klara.joyce@klarna.com",
    "street_address": "1 Safeway",
    "postal_code": "12345",
    "city": "Knoxville",
    "region": "TN",
    "phone": "+1 555-555-5555",
    "country": "us"
  },
  "selected_shipping_option": {
    "id": "free",
    "name": "Free Shipping",
    "description": "Your goods will be sent to the physical store closest to you",
    "promo": "",
    "price": 0,
    "tax_amount": 0,
    "tax_rate": 0,
    "preselected": false
  }
}

The response to the POST request should contain updated order_amount, order_tax_amount, and order_lines fields with tax amount and shipping costs added as order lines and to the total amount.

Note: During the callback the order will be locked for changes using the regular update call. Any updates must be done by responding to the callback

Example response:

{
  "order_amount": 1718,
  "order_tax_amount": 119,
  "order_lines": [
    {
      "type": "physical",
      "reference": "fH5aK96A",
      "name": "Blue T-Shirt",
      "quantity": 1,
      "unit_price": 1599,
      "tax_rate": 0,
      "total_amount": 1599,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "shipping_fee",
      "name": "Free Shipping",
      "quantity": 1,
      "unit_price": 0,
      "tax_rate": 0,
      "total_amount": 0,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "sales_tax",
      "reference": "Sales Tax",
      "name": "Sales Tax",
      "quantity": 1,
      "unit_price": 119,
      "tax_rate": 0,
      "total_amount": 119,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    }
  ]
}

 


Using the Klarna shipping selector

The Checkout allows the display and selection of shipping options as part of the Checkout flow. The shipping options may to be provided both at order creation and dynamically when the customer enters or changes the shipping address. The shippping options are provided as list a of shipping_option objects. 

Shipping options at order creation

By providing shipping options at order creation the shipping options will be displayed to the customer regardless of the address they enter. 

Shipping options provided at order creation can be updated or removed dynamically using the callback described below.

Example of the shipping_options field:

{
  "shipping_options": [
    {
      "id": "free",
      "name": "Free Shipping",
      "price": 0,
      "promo": "",
      "tax_amount": 0,
      "tax_rate": 0,
      "description": "Free shipping directly to your door",
      "preselected": true
    },
    {
      "id": "pickup",
      "name": "Pick up at closest store",
      "price": 385,
      "promo": "",
      "tax_amount": 35,
      "tax_rate": 1000,
      "description": "Your goods will be sent to the physical store closest to you."
    },
    {
      "id": "sameday",
      "name": "Same day delivery",
      "price": 275,
      "promo": "Only today! Available at a special price.",
      "tax_amount": 25,
      "tax_rate": 1000,
      "description": "Same day delivery directly to your door. Orders places after 4 PM will be delivered the following day."
    }
  ]
}

 

The selected shipping option

You will then receive the selected shipping option in the selected_shipping_option field as part of the completed order. The shipping cost will be added to the total amounts and the selected shipping option will be added to the order lines.

Example:

{
  "selected_shipping_option": {
    "id": "pickup",
    "name": "Pick up at closest store",
    "description": "Your goods will be sent to the physical store closest to you.",
    "promo": "",
    "price": 399,
    "tax_amount": 0,
    "tax_rate": 0,
    "preselected": false
  }
}

 

Dynamically update shipping options

Shipping options can be dynamically added, updated, or removed when the customer changes their shipping address using the address_update callback.

The address_update callback is activated by specifying the address_update URL as part of the merchant_urls object when the order is created. (Note that the URL must be a valid HTTPS URL)

During the callback the order lines, the shipping options, both of them, or neither of them can be updated. The response to the POST request will be handled as following

  1. If order_amount, order_tax_amount, and order_lines are present the order will be updated to match the new order items.
  2. If a list of shipping options is present the order will be updated and the Checkout will show the new shipping options.
  3. If the response is anything but a 200 OK, or the request takes more than 10 seconds, the purchase will be interrupted and an error message will be shown to the user and they will be instructed to try again.

Note: During the callback the order will be locked for changes using the regular update call. Any updates must be done by responding to the callback.

Example response:

{
  "shipping_options": [
    {
      "id": "free",
      "name": "Free Shipping",
      "price": 0,
      "promo": "",
      "tax_amount": 0,
      "tax_rate": 0,
      "description": "Free shipping directly to your door",
      "preselected": true
    },
    {
      "id": "pickup",
      "name": "Pick up at closest store",
      "price": 385,
      "promo": "",
      "tax_amount": 35,
      "tax_rate": 1000,
      "description": "Your goods will be sent to the physical store closest to you."
    },
    {
      "id": "sameday",
      "name": "Same day delivery",
      "price": 275,
      "promo": "Only today! Available at a special price.",
      "tax_amount": 25,
      "tax_rate": 1000,
      "description": "Same day delivery directly to your door. Orders places after 4 PM will be delivered the following day."
    }
  ]
}

 

Shipping option selected callback

If you provide the shipping_option_update URL as part of the merchant_urls object a POST request will be sent to that URL when the customer selects or changes shipping option.

This callback is optional to implement, even if you use the Checkout shipping selector. If no callback is provided Klarna will add the shipping option to the order totals and order lines. You will then receive the selected shipping option in the selected_shipping_option field.

The body of the POST request will contain these fields:

  • order_amount
  • order_tax_amount
  • order_lines
  • billing_address
  • shipping_address
  • selected_shipping_option

Example POST request:

{
  "order_amount": 1718,
  "order_tax_amount": 119,
  "order_lines": [
    {
      "type": "physical",
      "reference": "fH5aK96A",
      "name": "Blue T-Shirt",
      "quantity": 1,
      "unit_price": 1599,
      "tax_rate": 0,
      "total_amount": 1599,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "shipping_fee",
      "name": "Free Shipping",
      "quantity": 1,
      "unit_price": 0,
      "tax_rate": 0,
      "total_amount": 0,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "sales_tax",
      "reference": "Sales Tax",
      "name": "Sales Tax",
      "quantity": 1,
      "unit_price": 119,
      "tax_rate": 0,
      "total_amount": 119,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    }
  ],
  "billing_address": {
    "given_name": "Klara",
    "family_name": "Joyce",
    "email": "klara.joyce@klarna.com",
    "street_address": "1 Safeway",
    "postal_code": "12345",
    "city": "Knoxville",
    "region": "TN",
    "phone": "+1 614-981-5377",
    "country": "us"
  },
  "shipping_address": {
    "given_name": "Klara",
    "family_name": "Joyce",
    "email": "klara.joyce@klarna.com",
    "street_address": "1 Safeway",
    "postal_code": "12345",
    "city": "Knoxville",
    "region": "TN",
    "phone": "+1 614-981-5377",
    "country": "us"
  },
  "selected_shipping_option": {
    "id": "pickup",
    "name": "Pick up at closest store",
    "description": "Your goods will be sent to the physical store closest to you.",
    "promo": "",
    "price": 399,
    "tax_amount": 0,
    "tax_rate": 0,
    "preselected": false
  }
}

You should update the totals and order lines based on the selected shipping option and return this information in the response body. When receiving this information the Checkout will update and show the new total.

The response should contain:

  • order_amount
  • order_tax_amount
  • order_lines

Example response body:

{
  "order_amount": 2117,
  "order_tax_amount": 119,
  "order_lines": [
    {
      "type": "physical",
      "reference": "fH5aK96A",
      "name": "Blue T-Shirt",
      "quantity": 1,
      "unit_price": 1599,
      "tax_rate": 0,
      "total_amount": 1599,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "shipping_fee",
      "name": "Pick up at closest store",
      "quantity": 1,
      "unit_price": 399,
      "tax_rate": 0,
      "total_amount": 399,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    },
    {
      "type": "sales_tax",
      "reference": "Sales Tax",
      "name": "Sales Tax",
      "quantity": 1,
      "unit_price": 119,
      "tax_rate": 0,
      "total_amount": 119,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    }
  ]
}