Klarna change VAT depending on country

With the new VAT rules in EU we are trying to set VAT depending on what country user selects in Klarna.

Given the docs from Klarna:

https://developers.klarna.com/documentation/klarna-payments/in-depth-knowledge/puchase-countries-currencies-locales/

Note: If you also provide a country in the billing address object, this will override the sent in purchase_country of the session.

https://developers.klarna.com/documentation/klarna-checkout/use-cases/selling-to-multiple-countries/#one-url-covering-all-markets

  1. Allow the consumer to change the shipping country by enabling all your shipping countries on the checkout order.
  2. Use the address_update callback to keep shipping methods and prices updated.
  3. It is mandatory to use the country_change callback with this setup. When you receive a callback you need to consider to…
    3a Update the language, we recommend to match the billing address country if we support it.
    3b Update the currency to a local one if you are configured for it to get a core-country experience. Or update it to a non-local one to offer card.
  4. Set the purchase_country to a best guess of the consumers country.

Done

  1. I have added all my countries to shipping and billing properties when UpdateDataSentToKlarna is run.

  2. I have updated the call in address_update to check the checkoutOrderData?.BillingCheckoutAddress?.Country from Klarna and then update the purchase country (in 4).

    Issue here is how can I update the VAT for the order?

    Since this is an update from Klarna servers there is no _cartAccessor.
    I tried using ILitiumKcoOrder by fetching the order there and then set orderCarrier.CountryId to the new country and then run CalculateOrderTotals but the VAT is incorrect.

    I also tried to manully set VAT but that does not seem to work.

  3. This never seems to trigger (checked the logs in klarna but nothing, only address_update

// KlarnaPaymentConfig.cs
[HttpPost]
public ActionResult AddressUpdate(string accountId, string transactionNumber, [FromJsonBody] CheckoutOrder checkoutOrderData)
{
    _logger.LogDebug("Address update started accountId {accountId} transactionNumber {transactionNumber}.", accountId, transactionNumber);
    var klarnaCheckout = CreateKlarnaCheckoutApi(accountId);
    var checkoutOrder = klarnaCheckout.FetchKcoOrder(transactionNumber) as LitiumKcoOrder;;

    var result = _paymentConfigv3.AddressUpdate(checkoutOrder, ref checkoutOrderData);
    
    if (result.Updated)
        klarnaCheckout.UpdateKcoOrderOnDisc(result.CheckoutOrder.OrderCarrier);

    _logger.LogDebug("Completed.");
    //if the correct Json result is not written to Klarna, the checkout snippet will show and error!.
    return Content(_jsonSerializer.Serialize(checkoutOrderData));
}
// KlarnaPaymentConfigV3.cs
public (bool Updated, ILitiumKcoOrder CheckoutOrder) AddressUpdate(ILitiumKcoOrder checkoutOrder, ref CheckoutOrder checkoutOrderData)
{
    // If the campaigns depend on address location, re-calculate the cart, after updating delivery addresses.
    // if you recalculate order totals, the checkoutOrderData must contain the correct order rows and order total values.
    var checkoutCountry = checkoutOrderData?.BillingCheckoutAddress?.Country;
    if (string.IsNullOrWhiteSpace(checkoutCountry))
        return (false, null);

    var orderCarrier = checkoutOrder.OrderCarrier;
    if (orderCarrier == null)
        return (false, null);

    var country = _countryService.Get(checkoutCountry);

    orderCarrier.CountryID = country.SystemId;
    orderCarrier.OverallVatPercentage = 0;
    orderCarrier.OrderRows.ForEach(x =>
    {
        x.TotalVATAmount = 0;
        x.VATPercentage = 0;
    });
    
    _moduleECommerce.Orders.CalculateOrderTotals(orderCarrier, SecurityToken.CurrentSecurityToken);
    _moduleECommerce.Orders.CalculatePaymentInfoAmounts(orderCarrier, SecurityToken.CurrentSecurityToken);
    checkoutOrderData.PurchaseCountry = checkoutCountry;
    checkoutOrderData.OrderAmount = Convert.ToInt32(checkoutOrder.OrderCarrier.GrandTotal * 100);
    checkoutOrderData.OrderTaxAmount = Convert.ToInt32(checkoutOrder.OrderCarrier.TotalVAT * 100);
    
    foreach (var orderLine in checkoutOrderData.OrderLines)
    {
        orderLine.TaxRate = 0;
        orderLine.TotalTaxAmount = 0;
    }
    return (true, checkoutOrder);
}
private ExecutePaymentArgs CreatePaymentArgs(OrderCarrier order, string private ExecutePaymentArgs CreatePaymentArgs(OrderCarrier order, string paymentAccountId)
{
	var tlsUsage = _routeRequestLookupInfoAccessor.RouteRequestLookupInfo.IsSecureConnection;
	if (!tlsUsage)
	{
	    this.Log().Trace("Klarna Checkout Validation is disabled. To enable the validate you need to use https on the checkout page.");
	}
	// Get the fallback delivery methods that will be send to KSS
	var ids = _requestModelAccessor.RequestModel.ChannelModel?.Channel?.CountryLinks?.FirstOrDefault(x => x.CountrySystemId == order.CountryID)?.DeliveryMethodSystemIds ?? Enumerable.Empty<Guid>();
	var allDeliveryMethods = _moduleECommerce.DeliveryMethods.GetAll();
	var kssDeliveryMethod = allDeliveryMethods.FirstOrDefault(x => ConstantsAndKeys.KSSDeliveryMethodName.Equals(x.Name, StringComparison.OrdinalIgnoreCase));
	var kssDeliveryMethodId = kssDeliveryMethod != null && ids.Contains(kssDeliveryMethod.ID) ? kssDeliveryMethod.ID : Guid.Empty;
	var currentDeliveryMethods = kssDeliveryMethodId == Guid.Empty ?
	    ids.Select(x => allDeliveryMethods.First(y => y.ID == x)) :
	    (ids.Count() == 1 ? allDeliveryMethods.Where(x => x.ID == kssDeliveryMethodId) : ids.Where(z => z != kssDeliveryMethodId).Select(x => allDeliveryMethods.First(y => y.ID == x)));
	var deliveryMethodIds = currentDeliveryMethods.Select(z => z.ID).ToList();

	var checkoutFlowInfo = _cartAccessor.Cart.CheckoutFlowInfo;
	checkoutFlowInfo.ExecutePaymentMode = ExecutePaymentMode.Reserve;
	checkoutFlowInfo.RequireConsumerConfirm = false;

	var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);

	var checkoutPage = _requestModelAccessor.RequestModel.WebsiteModel.Fields.GetValue<PointerPageItem>(AcceleratorWebsiteFieldNameConstants.CheckoutPage);
	var checkoutPageUrl = GetAbsolutePageUrl(checkoutPage);
	var checkoutPageEntity = checkoutPage.EntitySystemId.MapTo<PageModel>();
	var termsAndConditionPage = checkoutPageEntity.GetValue<PointerPageItem>(CheckoutPageFieldNameConstants.TermsAndConditionsPage);
	var termsAndConditionPageUrl = GetAbsolutePageUrl(termsAndConditionPage);


	var proxy = ConfigurationManager.AppSettings["Proxy"] as string ?? string.Empty;

	var channelUri = new Uri(_urlService.GetUrl(_requestModelAccessor.RequestModel.ChannelModel.Channel, new ChannelUrlArgs() { AbsoluteUrl = true }));
	var confirmationUrl = new Uri(channelUri, urlHelper.Action("Confirmation", "KlarnaPayment", null, Uri.UriSchemeHttps));
	var validateUrl = new Uri(channelUri, urlHelper.Action("Validate", "KlarnaPayment", null, Uri.UriSchemeHttps)).ReplaceHost(proxy);
	var pushUrl = new Uri(channelUri, urlHelper.Action("PushNotification", "KlarnaPayment", null, Uri.UriSchemeHttps)).ReplaceHost(proxy);
	var updateAddressUrl = new Uri(channelUri, urlHelper.Action("AddressUpdate", "KlarnaPayment", null, Uri.UriSchemeHttps)).ReplaceHost(proxy);
	var shippingOptionUpdateUrl = new Uri(channelUri, urlHelper.Action("ShippingOptionUpdate", "KlarnaPayment", null, Uri.UriSchemeHttps)).ReplaceHost(proxy);
	var countryChangeUrl = new Uri(channelUri, urlHelper.Action("CountryChange", "KlarnaPayment", null, Uri.UriSchemeHttps)).ReplaceHost(proxy);

	checkoutFlowInfo.SetValue(ConstantsAndKeys.TermsUrlKey, termsAndConditionPageUrl);
	checkoutFlowInfo.SetValue(ConstantsAndKeys.CheckoutUrlKey, checkoutPageUrl);
	checkoutFlowInfo.SetValue(ConstantsAndKeys.ConfirmationUrlKey, confirmationUrl.AbsoluteUri);
	checkoutFlowInfo.SetValue(ConstantsAndKeys.PushUrlKey, pushUrl.AbsoluteUri);
	checkoutFlowInfo.SetValue(ConstantsAndKeys.ShippingOptionUpdateUrlKey, shippingOptionUpdateUrl.AbsoluteUri);

	checkoutFlowInfo.SetValue(ConstantsAndKeys.ValidationUrlKey, validateUrl.AbsoluteUri);
	checkoutFlowInfo.SetValue(ConstantsAndKeys.DeliveryMethodIds, deliveryMethodIds);
	checkoutFlowInfo.SetValue(ConstantsAndKeys.CountryChangeUrlKey, countryChangeUrl.AbsoluteUri);
	checkoutFlowInfo.SetValue(ConstantsAndKeys.AddressUpdateUrlKey, updateAddressUrl.AbsoluteUri);

	return new KlarnaPaymentArgsCreator().CreatePaymentArgs(checkoutFlowInfo);
}

CreateOrder

Does not seam to add countryChanged callback, could be why it does not call anything.

"merchant_urls": {
    "terms": "https://casall.localtest.me/en-eu/",
    "checkout": "https://casall.localtest.me/en-eu/checkout?accountId=KCOV3_EU&transactionNumber={checkout.order.id}",
    "confirmation": "https://casall.localtest.me/site.axd/KlarnaPayment/Confirmation?accountId=KCOV3_EU&transactionNumber={checkout.order.id}",
    "push": "https://945f543732a7.ngrok.io/site.axd/KlarnaPayment/PushNotification?accountId=KCOV3_EU&transactionNumber={checkout.order.id}",
    "validation": "https://945f543732a7.ngrok.io/site.axd/KlarnaPayment/Validate?accountId=KCOV3_EU&transactionNumber={checkout.order.id}",
    "shipping_option_update": "https://945f543732a7.ngrok.io/site.axd/KlarnaPayment/ShippingOptionUpdate?accountId=KCOV3_EU&transactionNumber={checkout.order.id}",
    "address_update": "https://945f543732a7.ngrok.io/site.axd/KlarnaPayment/AddressUpdate?accountId=KCOV3_EU&transactionNumber={checkout.order.id}",
    "notification": "https://casall.localtest.me/PaymentProviderResult.axd/StaticCallback/KlarnaV3?accountId=KCOV3_EU&transactionNumber={checkout.order.id}"
  },

Address update

{
  "purchase_country": "be",
  "purchase_currency": "EUR",
  "locale": "en-GB",
  "billing_address": {
    "given_name": "Artur",
    "family_name": "O",
    "email": "mail@gmail.com",
    "street_address": "Kerklosstraat 161",
    "postal_code": "9700",
    "city": "Bevere",
    "phone": "+32 491 77 20 61",
    "country": "be"
  },
  "shipping_address": {
    "given_name": "Artur",
    "family_name": "Olech",
    "email": "mail@gmail.com",
    "street_address": "Kerklosstraat 161",
    "postal_code": "9700",
    "city": "Bevere",
    "phone": "+32 491 77 20 61",
    "country": "be"
  },
  "order_amount": 6892,
  "order_tax_amount": 0,
  "order_lines": [
    {
      "type": "physical",
      "reference": "21124901038",
      "name": "Scuba Zip Bikini Top - Black",
      "quantity": 1,
      "quantity_unit": "nos",
      "unit_price": 7990,
      "tax_rate": 0,
      "total_amount": 7990,
      "total_discount_amount": 0,
      "total_tax_amount": 0,
      "shipping_attributes": {
        "weight": 110
      }
    },
    {
      "type": "shipping_fee",
      "reference": "0",
      "name": "KSS",
      "quantity": 1,
      "quantity_unit": "nos",
      "unit_price": 500,
      "tax_rate": 0,
      "total_amount": 500,
      "total_discount_amount": 0,
      "total_tax_amount": 0
    }
  ],
  "customer": {},
  "selected_shipping_option": {
    "id": "a805b491-5db1-4b3a-9221-4b30026489ac",
    "name": "UPS Standard (Non Docs)",
    "price": 500,
    "tax_amount": 0,
    "tax_rate": 0,
    "preselected": false,
    "shipping_method": "Home",
    "delivery_details": {
      "carrier": "ups",
      "class": "standard"
    }
  },
  "recurring": false
}

image

Litium version: 7.6.1

Yeah, the callback for CountryChanged doesn’t seem to be supported. I added a bug report: https://docs.litium.com/support/bugs/bug_details?id=55791

You should still be able to use _cartAccessor, only that it will give you back an empty cart. Since we have an order carrier for the KCO order, we can just set the carts order carrier to that one. Then we can call SetChannel with a new country, which will trigger a re-creation of all order rows.

This is a quite dirty example, so should probably clean it up a bit. I have Sweden set up with 25% VAT and Norway with 12% VAT. Norway on the left below.

public ActionResult AddressUpdate(string accountId, string transactionNumber, [FromJsonBody] CheckoutOrder checkoutOrderData)
{
	this.Log().Debug("Address update started accountId {accountId} transactionNumber {transactionNumber}.", accountId, transactionNumber);
	_paymentConfigv3.AddressUpdate(checkoutOrderData);

	var klarnaCheckout = CreateKlarnaCheckoutApi(accountId);
	var checkoutOrder = klarnaCheckout.FetchKcoOrder(transactionNumber) as LitiumKcoOrder;
	var country = checkoutOrderData?.BillingCheckoutAddress?.Country;

	var orderCarrier = checkoutOrder.OrderCarrier;
	var orderCarrierCountry = _countryService.Get(orderCarrier.CountryID);

	if (!country.Equals(orderCarrierCountry.Id, StringComparison.OrdinalIgnoreCase))
	{
		var klarnaCountry = _countryService.Get(country);

		// get a new cart with an empty carrier
		var cart = _cartAccessor.Cart; 
		var channel = _channelService.Get(orderCarrier.ChannelID);

		// set carts carrier to stored carrier in Klarna
		cart.OrderCarrier = orderCarrier; 

		// this will re-create individual order rows and then calculate order total
		cart.SetChannel(channel, klarnaCountry, SecurityToken.CurrentSecurityToken); 

		ModuleECommerce.Instance.Orders.CalculatePaymentInfoAmounts(cart.OrderCarrier, SecurityToken.CurrentSecurityToken);

		klarnaCheckout.UpdateKcoOrderOnDisc(cart.OrderCarrier);
		checkoutOrderData.PopulateOrderLines(cart.OrderCarrier, cart.OrderCarrier.PaymentInfo.First());
	}

	this.Log().Debug("Completed.");
	//if the correct Json result is not written to Klarna, the checkout snippet will show and error!.
	var jsonSerializer = new Klarna.Rest.Core.Serialization.JsonSerializer();
	return Content(jsonSerializer.Serialize(checkoutOrderData), "application/json");
}

Please note the updated return. Related bug: https://docs.litium.com/support/bugs/bug_details?id=54352

Hi thanks!

This seems to work somewhat.

  1. One issue is if customer already has Klarna address set and goes to checkout. This won’t trigger the update
  2. Second issue is that if you refresh the page it will reload the incorrect VAT

Both I think we need to add a check when SendDataToKlarna method is run to update it there I think too.

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.