Klarna checkout - using React & Node

1.4k views Asked by At

I'm trying to implement Klarna checkout to accept payments.

When creating an order (first step of the checkout) you receive a "html_snippet" as response from Klarnas API (in my case to express backend). It requires that this snippet is shown in the front end (in my case react).

Having read Klarnas documentation, I still cant figure out how to show this html snippet in react. Even copy pasting the exact snippet gives me 41 error warnings.

The snippet looks like this:


 html_snippet: '<div id="klarna-checkout-container" style="overflow: hidden;">\n' +
      '  <div id="klarna-unsupported-page">\n' +
      '  <style type="text/css">\n' +
      '  @-webkit-keyframes klarnaFadeIn{from{opacity:0}to{opacity:1}}@-moz-keyframes klarnaFadeIn{from{opacity:0}to{opacity:1}}@keyframes klarnaFadeIn{from{opacity:0}to{opacity:1}}#klarna-unsupported-page{opacity:0;opacity:1\\9;-webkit-animation:klarnaFadeIn ease-in 1;-moz-animation:klarnaFadeIn ease-in 1;animation:klarnaFadeIn ease-in 1;-webkit-animation-fill-mode:forwards;-moz-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-duration:.1s;-moz-animation-duration:.1s;animation-duration:.1s;-webkit-animation-delay:5s;-moz-animation-delay:5s;animation-delay:5s;text-align:center;padding-top:64px}#klarna-unsupported-page .heading{font-family: "Klarna Headline", Helvetica, Arial, sans-serif;color: rgb(23, 23, 23);font-size: 36px;letter-spacing: -0.2px;-webkit-font-smoothing: antialiased;}#klarna-unsupported-page .subheading{font-family: "Klarna Text", "Klarna Sans", Helvetica, Arial, sans-serif;color: rgb(23, 23, 23);-webkit-font-smoothing: antialiased;line-height: 28px;font-weight: 400;font-size: 19px;max-width: 640px;margin: 20px auto;}#klarna-unsupported-page .reload {cursor: pointer;outline: none;-webkit-tap-highlight-color: rgba(255, 255, 255, 0);border-width: 1px;background-color: rgb(38, 37, 37);border-color: rgb(38, 37, 37);padding: 15px 24px;margin-top: 15px;color: rgb(255, 255, 255);font-family: "Klarna Text", "Klarna Sans", Helvetica, Arial, sans-serif;font-weight: 500;text-rendering: geometricprecision;font-size: 100%;}\n' +
      '  </style>\n' +
      '  <h1 class="heading">Something went wrong</h1>\n' +
      '  <p class="subheading">Sorry for any inconvenience, please try reloading the checkout page or try again later.</p>\n' +
      '  <p class="subheading">If the problem persists it maybe be because you are using an old version of the web browser which is not safe nor compatible with modern web sites. For a smoother checkout experience, please install a newer browser.</p>\n' +     
      '  <button class="reload" onclick="reloadCheckoutHandler && reloadCheckoutHandler()">Reload checkout</button>\n' +
      '  </div>\n' +
      '  <script id="klarna-checkout-context" type="text/javascript">\n' +
      '  /* <![CDATA[ */\n' +
      '  var reloadCheckoutHandler;\n' +
      '  (function(w,k,i,d,n,c,l){\n' +
      '    w[k]=w[k]||function(){(w[k].q=w[k].q||[]).push(arguments)};\n' +
      '    l=w[k].config={\n' +
      '      container:w.document.getElementById(i),\n' +
      "      ORDER_URL:'https://js.playground.klarna.com/eu/kco/checkout/orders/dae844ba-c44a-6e55-b5ad-9afe9055e1f4',\n" +
      "      AUTH_HEADER:'KlarnaCheckout 4mpla37op0i19pv6g054',\n" +
      "      LOCALE:'en-GB',\n" +
      "      ORDER_STATUS:'checkout_incomplete',\n" +
      "      MERCHANT_NAME:'Your business name',\n" +
      '      GUI_OPTIONS:[],\n' +
      '      ALLOW_SEPARATE_SHIPPING_ADDRESS:false,\n' +
      "      PURCHASE_COUNTRY:'gbr',\n" +
      "      PURCHASE_CURRENCY:'GBP',\n" +
      '      TESTDRIVE:true,\n' +
      "      BOOTSTRAP_SRC:'https://js.playground.klarna.com/kcoc/220119-c2c224d/checkout.bootstrap.js',\n" +
      "      FE_EVENTS_DISABLED:'false',\n" +
      "      DEVICE_RECOGNITION_URL:'https://js.playground.klarna.com/eu/kco/checkout/orders/dae844ba-c44a-6e55-b5ad-9afe9055e1f4/device_recognition',\n" +
      '      DEVICE_RECOGNITION_TYPE1:true,\n' +
      "      DEVICE_RECOGNITION_TYPE3_ORG_ID:'87rxrdob',\n" +
      "      DEVICE_RECOGNITION_TYPE3_REF:'KLRNA_87rxrdob_dae844ba-c44a-6e55-b5ad-9afe9055e1f4',\n" +
      "      CLIENT_EVENT_HOST:'https://eu.playground.klarnaevt.com'\n" +
      '    };\n' +
      "    n=d.createElement('script');\n" +
      '    c=d.getElementById(i);\n' +
      '    n.async=!0;\n' +
      '    n.src=l.BOOTSTRAP_SRC;\n' +
      '    c.appendChild(n);\n' +
      '    try{\n' +
      "      ((w.Image && (new w.Image))||(d.createElement && d.createElement('img'))||{}).src =\n" +
      "        l.CLIENT_EVENT_HOST + '/v1/checkout/snippet/load' +\n" +
      "        '?sid=' + l.ORDER_URL.split('/').slice(-1) +\n" +
      "        '&order_status=' + w.encodeURIComponent(l.ORDER_STATUS) +\n" +
      "        '&timestamp=' + (new Date).getTime();\n" +
      '    }catch(e){}\n' +
      '    reloadCheckoutHandler = function () {\n' +
      '        try{\n' +
      "            ((w.Image && (new w.Image))||(d.createElement && d.createElement('img'))||{}).src =\n" +
      "            l.CLIENT_EVENT_HOST+'/v1/checkout/snippet/reload?sid='+l.ORDER_URL.split('/').slice(-1)+\n" +
      "            '&order_status='+w.encodeURIComponent(l.ORDER_STATUS)+'&timestamp='+(new Date()).getTime();\n" +
      '            window.location.reload();\n' +
      '        }catch(e){}\n' +
      '    }\n' +
      "  })(this,'_klarnaCheckout','klarna-checkout-container',document);\n" +
      '  /* ]]> */\n' +
      '  </script>\n' +
      '  <noscript>\n' +
      'Please <a href="http://enable-javascript.com">enable JavaScript</a>.\n' +
      '  </noscript>\n' +
      '</div>'

Anyone been through this journey?

3

There are 3 answers

1
rads70 On

You may have already solved this, but I used Iframe to render the snippet. Make an HTML page with the received snippet inside and use that as the source for your Iframe.

<iframe
      title='klarnaCheckout'
       className='iframe'
       srcDoc={klarnaHtml(klarna.html_snippet)}
       frameBorder='0'
 ></iframe>

export const klarnaHtml = (snippet) => {
   return `
      <html>
      <head> </head>
   
      <body>
      <textarea style="display: none" id="KCO">
               ${snippet}
       </textarea
         >
   
         <div id="my-checkout-container"></div>
   
         <!-- START - Dont edit -->
         <script type="text/javascript">
            var checkoutContainer = document.getElementById(
               "my-checkout-container"
            );
            checkoutContainer.innerHTML = document
               .getElementById("KCO")
               .value.replace(/\\"/g, '"')
               .replace(/\\n/g, "");
            var scriptsTags = checkoutContainer.getElementsByTagName("script");
            for (var i = 0; i < scriptsTags.length; i++) {
               var parentNode = scriptsTags[i].parentNode;
               var newScriptTag = document.createElement("script");
               newScriptTag.type = "text/javascript";
               newScriptTag.text = scriptsTags[i].text;
               parentNode.removeChild(scriptsTags[i]);
               parentNode.appendChild(newScriptTag);
            }
         </script>
         <!-- END -->
      </body>
   </html>
   
      `;
};
1
Mian Usman Javed On

I Successfully Integrated the Klarna Payment Method in React.JS & Next.JS 14

"use client";
import React from "react";
import Script from "next/script";

export default function SortableTree() {
  return (
    <>
      <Script
        async
        src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"
      />
      <div id="klarna_container">
        <div>
          <button className="authorize">Buy Now</button>
        </div>
      </div>
      <Script async src="https://x.klarnacdn.net/kp/lib/v1/api.js" />
      <Script async id="klarna_container" type="text/javascript">
        {`  
        const script = document.createElement('script');
        script.src = 'https://code.jquery.com/jquery-3.3.1.min.js'; // Use a CDN or your local copy
        script.async = true;
        script.onload = () => {
          // jQuery is now loaded
          $(function ($) {
            window.klarnaAsyncCallback = function () {
              Klarna.Payments.init({
                client_token: 'CLIENT TOKEN HERE___',
              });
    
              Klarna.Payments.load({
                container: '#klarna_container',
                payment_method_category: 'pay_over_time',
              }, function (res) {
                console.log("Load function called");
                console.debug(res);
              });
    
              // Additional Klarna.Payments.load for multiple widgets
    
              $("button.authorize").on('click', function () {
                Klarna.Payments.authorize({
                  payment_method_category: "pay_over_time"
                }, {
                  purchase_country: "DE",
                  purchase_currency: "EURGBP",
                  locale: "en-GB",
                  // ... (rest of your authorize configuration)
                }, function (res) {
                  console.log("Response from the authorize call:");
                  console.log(res);
                })
              });
            };
    
            // Call your klarnaAsyncCallback after jQuery is loaded
            window.klarnaAsyncCallback();
          });
        };
    
        document.head.appendChild(script);
        `}
      </Script>
    </>
  );
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="hhttps://x.klarnacdn.net/kp/lib/v1/api.js"></script>
0
realmers On

using this package https://www.npmjs.com/package/dangerously-set-html-content

you can render the html_snippet like this:

const [html_snippet, setHtmlSnippet] = useState("");
<div>
        {html_snippet && <DangerouslySetHtmlContent html={html_snippet} />}
</div>

Where the html_snippet is gained from an api.

I use the dangerously-set-html-content package because the default behavior of dangerouslySetInnerHTML in react does not run the scripts which are gained from the external snippet.