0

I am trying to integrate Klarna Payments into my Angular site but the examples are provided in plain JS and jQuery, which I am not so familiar with.

What I need to do: Import Klarna's JS script and call 2 functions within it, Init and Load. Then on a button click I need to call the Authorize function. There may be multiple buttons and if so there will be a Load and Authorize function for each, this is dynamic. I then need to get the token returned by the Authorize function in order to pass to my backend.

Here's the example provided by Klarna:

<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script type="text/javascript" src="https://x.klarnacdn.net/kp/lib/v1/api.js" async></script>
</head>
<body>
    
<script type="text/javascript">
//The following method initializes the Klarna Payments JS library
    window.klarnaAsyncCallback = function () {
        Klarna.Payments.init({ 
            client_token: ''
        });
        console.log("Payments initialized");
//The following method loads the payment_method_category in the container with the id of 'klarna_container'
        Klarna.Payments.load({
        container: '#klarna_container',
            payment_method_category: ''
            
        }, function (res) {
               console.log("Load function called")
                console.debug(res);
        });
    };


/*The following is the authorize function, which triggers Klarna to perform a risk assessment of the purchase 
  The successful response of this risk assessment is an authorization token, which in this example is logged in the console
*/
  $(function(){
    $("button.authorize").on('click', function(){
        Klarna.Payments.authorize({
            payment_method_category: ''
            }, {
              billing_address: {
                given_name: "Jane",
                family_name: "Doe",
                email: "jane@doe.com",
                title: "Ms",
                street_address: "512 City Park Ave",
                postal_code: "43215",
                city: "Columbus",
                region: "oh",
                phone: "6142607295",
                country: "US"
              },
              order_amount: 20000,
              order_tax_amount: 0,
              order_lines: [{
                type : "physical",
                reference : "19-402",
                name : "black T-Shirt",
                quantity : 2,
                unit_price : 5000,
                tax_rate : 0,
                total_amount : 10000,
                total_discount_amount : 0,
                total_tax_amount : 0
            },
            {
            type : "physical",
            reference : "19-402",
            name : "red trousers",
            quantity : 1,
            unit_price : 10000,
            tax_rate : 0,
            total_amount : 10000,
            total_discount_amount : 0,
            total_tax_amount : 0
            }],
             }, function(res) {
                console.log("Response from the authorize call:")
                console.log(res)
              
            })
    })
  })
</script>


<div style="width: 500px; margin: auto; padding-top: 150px; padding-bottom: 30px;">
    <img src="https://x.klarnacdn.net/payment-method/assets/badges/generic/klarna.svg" style="width: 500px; margin: auto;"> 
</div>

<!--Klarna container-->
<div id="klarna_container" style="width: 500px; margin: auto;"></div>
<div style="width: 500px; margin: auto;">
    <!--Button to trigger authorize call-->
    <button class="authorize" style="width: 500px; height: 50px; margin: auto;">Your Buy Button</button>
</div>

</body>
</html>

And here's what I've tried so far (all on one component for now while testing. Will be split into services or whatever once it's working)

test.component.html

<p>testing...</p>

<div class="klarna-widget">
  <div id="klarna-payments-container" style="width: 500px; margin: auto;">
    <!-- In future there will be multiples of this div & contents with different IDs -->

    <div style="width: 500px; margin: auto;">
      <!--Button to trigger authorize call-->
      <button class="authorize" style="width: 500px; height: 50px; margin: auto;">Your Buy Button</button>
    </div>
  </div>
</div>

test.component.ts

import { DOCUMENT } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
import { KlarnaSession } from 'src/app/payment/KlarnaObjects';
import { GlobalConstants } from 'src/app/shared/global-constants';

// eslint-disable-next-line @typescript-eslint/ban-types
declare let Klarna: Function;

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
  klarnaSession: KlarnaSession;

  constructor(private http: HttpClient,
              private renderer: Renderer2,
              @Inject(DOCUMENT) private document: Document) { }

  ngOnInit(): void {

    this.http.get<KlarnaSession>(`${GlobalConstants.apiUrl}/Payment/GetKlarnaSession`).subscribe(
      (result: KlarnaSession) => {
        this.loadKlarnaSDK(result);
      }
    );
  }

  loadKlarnaSDK(session: KlarnaSession) {
    const scriptjq = this.renderer.createElement('script');
    scriptjq.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js';
    this.renderer.appendChild(this.document.body, scriptjq);

    const script1 = this.renderer.createElement('script');
    script1.src = 'https://x.klarnacdn.net/kp/lib/v1/api.js';
    script1.async = true;
    this.renderer.appendChild(this.document.body, script1);

    const script2 = this.renderer.createElement('script');
    script2.text = `window.klarnaAsyncCallback = function () {
      Klarna.Payments.init({
        client_token: '${session.client_token}'
      });

      // In the real thing, I'll need to call this for each item in session.payment_method_categories[].
      // Same for the authorize function.
      Klarna.Payments.load({
        container: 'klarna-payments-container',
        payment_method_category: '${session.payment_method_categories[0].identifier}'
      }, function (res) {
        console.log('Load function called');
        console.debug(res);
      });
    };`;
    this.renderer.appendChild(this.document.body, script2);
  }

}

The Klarna.Payments.load doesn't work, before even trying the authorize. I'm not even sure if the Init is being called.

Console gives this error:

Uncaught InvalidContainerSelectorError: The container selector is invalid. Please, check that the used ID or CSS class name is correct and that it targets an existing DOM element.

even though the container name klarna-payments-container is definitely correct.

How should this be done in Angular? Am I on the wrong track completely?


After sorting the selector problem there is still an issue with it recognising the authorize function.

Updated ts file:

/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { DOCUMENT } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
import { KlarnaSession } from 'src/app/payment/KlarnaObjects';
import { GlobalConstants } from 'src/app/shared/global-constants';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
  klarnaSession: KlarnaSession;

  constructor(private http: HttpClient,
              private renderer: Renderer2,
              @Inject(DOCUMENT) private document: Document) { }

  ngOnInit(): void {

    this.http.get<KlarnaSession>(`${GlobalConstants.apiUrl}/Payment/GetKlarnaSession`).subscribe(
      (result: KlarnaSession) => {
        this.loadKlarnaSDK(result);
      }
    );
  }

  loadKlarnaSDK(session: KlarnaSession) {
    const scriptjq = this.renderer.createElement('script');
    scriptjq.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js';
    this.renderer.appendChild(this.document.body, scriptjq);

    const script1 = this.renderer.createElement('script');
    script1.src = 'https://x.klarnacdn.net/kp/lib/v1/api.js';
    script1.async = true;
    this.renderer.appendChild(this.document.body, script1);

    const script2 = this.renderer.createElement('script');
    script2.text = `window.klarnaAsyncCallback = function () {
      Klarna.Payments.init({
        client_token: '${session.client_token}'
      });

      Klarna.Payments.load({
        container: '#klarna-payments-container',
        payment_method_category: '${session.payment_method_categories[0].identifier}'
      }, function (res) {
        console.log('Load function called');
        console.debug(res);
      });
    };

    $(function() {
      $("button.authorize").on('click', function() {
        Klarna.Payments.authorize({
          payment_method_category: '${session.payment_method_categories[0].identifier}'
        }, {
          // ORDER DETAILS GO HERE
        }, function(res) {
          console.log("Response from the authorize call:")
          console.log(res)
        })
      })

    })`;
    this.renderer.appendChild(this.document.body, script2);
  }

}

It complains about the first line $(function() {...

Uncaught ReferenceError: $ is not defined

Sparrowhawk
  • 358
  • 1
  • 4
  • 11
  • The error message says the word _selector_, so I would try to actually turn that into a valid CSS selector. Looking at the html that you included, `klarna-payments-container` is an id, so the selector for that would be `#klarna-payments-container`. Try using this one instead, with a hashbang prefix. – Octavian Mărculescu Apr 28 '22 at 09:57
  • Doh, thank you, that sorted the load. However it's still not working at the next step, see the updated question. – Sparrowhawk Apr 28 '22 at 11:11
  • Does the jQuery script load? You can look in the console for errors, or better yet in the network tab, to see if the `jquery.min.js` file is being pulled in. – Octavian Mărculescu Apr 28 '22 at 11:33
  • That looks fine in the network tab. No console errors apart from this one. – Sparrowhawk Apr 28 '22 at 15:05

0 Answers0