5

I am trying to integrate Worldpay in an angular2 app.

I am using own form (own-form) approach where it is necessary to include their script in page: <script src="https://cdn.worldpay.com/v1/worldpay.js"></script> Add specific attibutes for some inputs: data-worldpay and attach the Worldpay.js logic to the form...

I managed to make the steps:
1. Include Worldpay.js in your page
2. Create a payment form with the relevant attributes

How can I continue to make next steps... I am stuck on that problem:

5. Attach Worldpay.js to your form:

<script type="text/javascript">
var form = document.getElementById('paymentForm');

Worldpay.useOwnForm({
  'clientKey': 'your-test-client-key',
  'form': form,
  'reusable': false,
  'callback': function(status, response) {
    document.getElementById('paymentErrors').innerHTML = '';
    if (response.error) {             
      Worldpay.handleError(form, document.getElementById('paymentErrors'), response.error); 
    } else {
      var token = response.token;
      Worldpay.formBuilder(form, 'input', 'hidden', 'token', token);
      form.submit();
    }
  }
});
</script>

Why?
angular2 removes all tags <script from templates.
Supposing with a workaround it would be possible to inject some scripts in page in ngAfterViewInit() method (like i did for 1st step)

ngAfterViewInit(): void {        
  let s = document.createElement("script");
  s.type = "text/javascript";
  s.src = "https://cdn.worldpay.com/v1/worldpay.js";
  this.worldPayScriptElement.nativeElement.appendChild(s);        
}

where this.worldPayScriptElement is a ViewChild of the div from template: <div #worldPayScriptElement hidden></div>

But, As result of their processing rules, worldpay will replace sensitive data from my form with a field called CreditCardToken

From source: Finally, in Worldpay.formBuilder() all sensitive card data is removed from the form, replaced with the token and only then is the form submitted back to your server. source: https://developer.worldpay.com/jsonapi/docs/own-form

How to continue integrating this ... Cannot understand.
If they would have an API returning the CreditCardToken based on a GET/POST request it would be perfect, but from documentation I didn't found yet the right method for that...

I will really appreciate any kind of suggestions.

mihai
  • 2,746
  • 3
  • 35
  • 56
  • How did you get the client key? – artsnr May 31 '22 at 12:52
  • wooh, it was ~5 years ago... I didn't check now, but I think to obtain a client key you should register an account in a way something like that: - Create your account https://online.worldpay.com - verify your email to be able to get the API keys needed to set up the integration... -> Here you'll see that there are two keys - Client key and Service key. – mihai Jun 09 '22 at 12:49

3 Answers3

3

I have resolved that considering another approach :)

I have used Worldpay API to get the token.
API url: https://api.worldpay.com/v1/tokens

The endpoint waits a POST request in the form:

'{
    "reusable": true/false,
    "paymentMethod": {
        "name": "name",
        "expiryMonth": 2,
        "expiryYear": 2015,
        "issueNumber": 1,
        "startMonth": 2,
        "startYear": 2013,
        "cardNumber": "4444 3333 2222 1111",
        "type": "Card",
        "cvc": "123"
    },
    "clientKey": "T_C_client_key"
}'

where the Header should contain these options: "Content-type: application/json"

Having that, no more need to include worldpay.js in page.
Also, no more need to include in payment form worldpay specific attributes (like data-worldpay="")

Simply should invoke the API, wait for the response, which will have the form:

{
    "token": "UUID of token",
    "reusable": true/false,
    "paymentMethod": {
        "type" : "ObfuscatedCard",     
        "name": "name",
        "expiryMonth": 2,
        "expiryYear": 2015,
        "issueNumber": 1,
        "startMonth": 2,
        "startYear": 2013,
        "cardType": "VISA_CREDIT",
        "maskedCardNumber": "xxxx xxxx xxxx 1111",
        "cardSchemeType": "consumer",
        "cardSchemeName": "VISA CREDIT",
        "cardProductTypeDescNonContactless": "Visa Credit Personal",
        "cardProductTypeDescContactless": "CL Visa Credit Pers",
        "cardIssuer": "LLOYDS BANK PLC",
        "countryCode": "GBR",
        "cardClass": "credit",
        "prepaid": "false"
    }
}

From response you are ready to use the response.token in order to proceed to next step: payment.

You should make sure that specific WorldPay attributes will be sent (CreditCardToken, Enrolled)

How I invoked the worldpay API in angular2?

public getWorldpayToken(request: any): Observable<any>{
    let worldPayApiUrl = `https://api.worldpay.com/v1/tokens`;
    let body = JSON.stringify(request);
    let headers = new Headers({ 'Content-Type':'application/json;charset=UTF-8'});
    let options = new RequestOptions({ headers: headers });

    return this.http.post(worldPayApiUrl, body, options)
      .map(response => response.json())
      .catch(this.handleError);
}

Documentation: https://developer.worldpay.com/jsonapi/api#tokens

For any other details, do not hesitate to comment/ask :)

mihai
  • 2,746
  • 3
  • 35
  • 56
  • Yeah. That's great if you are PCI compliant and are happy to process the credit card details otherwise you will have to resort to using worldpay in an iframe or send the user over to the worldpay site. – Kamalpreet May 18 '17 at 10:05
  • ...boring... they didn't implement a wrapper for angular2 yet ? (I didn't check after I implemented the solution as I wrote above) – mihai May 18 '17 at 14:22
  • "they" I mean worldpay :) – mihai May 18 '17 at 15:12
3

The accepted answer is great if you can use the API but if you are not PCI-DSS compliant you should not use the API.

For using the template form or your own form in Angular 2+ the steps I've used are as follows:

  1. Include Worldpay.js somehow, either:

    • A local copy as a script in .angular-cli.json
    • A local copy via import in your component e.g. import '../../libs/worldpay';
    • Include a script tag in the main index.html loading it from the cdn:
      <script src="https://cdn.worldpay.com/v1/worldpay.js"></script>
  2. Satisfy Typescript that it is available in your component:

    declare var Worldpay: any;   
    
  3. Use a ViewChild to get a reference to the form:

    @ViewChild('paymentForm') form;
    

    and in the html:

    <form #paymentForm>
    
  4. Implement a callback, for example:

    worldpayCallback(status, response) {        
      if (response.error) {
        this.handleError(response.error.object);
      } else {
        this.token = response.token;
      }
    }
    
  5. Initialise Worldpay in a suitable hook like:

    ngOnInit(): void {
      Worldpay.useTemplateForm({
        'clientKey': this.worldpayKey,
        'form': this.form.nativeElement,
        'callback': (status, response) => this.worldpayCallback(status, response)
      });
    }    
    

You should now have the token available to you after the form is submitted.

Note that if you are using your own form you need to become PCI SAQ A-EP compliant which involves a self assessment process (lengthy paperwork).

Brian Smith
  • 3,383
  • 30
  • 41
2

This is a working example in Angular 4/5. Please add your client api key to Worldpay.component.ts

https://plnkr.co/edit/vz6YX68ykyBuHGiwGBVx

Template:

<form #paymentForm *ngIf="!token">
    <div id="paymentSection"></div>
</form>
<h2 *ngIf="token">Token from WorldPay {{ token }}</h2>

Component:

import { Component, OnInit, ViewChild } from '@angular/core';
declare var Worldpay: any;

@Component({
  selector: 'app-worldpay',
  templateUrl: './worldpay.component.html'
})

export class WorldpayComponent implements OnInit {

  @ViewChild('paymentForm') form;
  readonly worldpayClientKey = 'ADD_YOUR_KEY_HERE';
  token: string = null;

  constructor() { }

  ngOnInit() {
    this.loadScript('https://cdn.worldpay.com/v1/worldpay.js', this.init);
  }

  init = (): void => {
    Worldpay.useTemplateForm({
      'clientKey': this.worldpayClientKey,
      'form': this.form.nativeElement,
      'paymentSection': 'paymentSection',
      'display': 'inline',
      'type': 'card',
      'callback': this.worldpayCallback
    });
  }

  worldpayCallback = (status): void => {
    this.token = status.token;
  }

  private loadScript(url: string, callbackFunction: (any) = undefined) {
    const node = document.createElement('script');
    node.src = url;
    node.type = 'text/javascript';
    node.onload = callbackFunction;
    document.getElementsByTagName('body')[0].appendChild(node);
  }
}
Zymotik
  • 6,412
  • 3
  • 39
  • 48