Client Side Encryption (CSE) with 3DS Flex

This guide describes how you might integrateCSEalongside3DS Flex.

Prerequisite:

  • You must use the custom version ofCSE.
  • You need to be setup for 3DS Flex, before using it. For more information, contact your Relationship Manager.
  • You have already created yourRSA key.

On this page:

Preparing your payment page

You must integrate the Worldpay CSE library as well as performing Device Data Collection (DDC). This allows you to obtain the following values you must add to your payment form and subsequently send to Worldpay.

  • dfReferenceId

  • <encryptedData>

Below is an example of a payment form including the two fields. It also contains a custom JavaScript onclick event which will bechanged later.

Copied!
<form id="paymentDetailsForm" action="[YOUR_ACTION]" method="POST">
    <input id="cardNumber"/> 
    <input id="cardHolderName"/> 
    <input id="expiryMonth"/> 
    <input id="expiryYear"/> 
    <input id="cvcCode"/> 
    <input type="hidden" name="dfReferenceId" id="dfReferenceId" value=""/><!-- The SessionId returned in the `postMessage` of DDC -->
    <input type="hidden" name="encryptedData" id="encryptedData" value=""/>
    <input id="payButton" type="button" value="Pay" onclick="custom_submit_action();"/>
</form>

Warning: Do not use the "name" HTML attribute in your payment form unless you want to pass card data to your server unencrypted.

Device Data Collection (DDC)

This step tells you how you retrieve the dfReferenceId you must add to your payment form.

JWT creation

All requests to Cardinal Commerce from the shopper's browser must be authenticated using a JSON Web Token (JWT). Providing this gives the shopper's browser access to resources to complete DDC and Challenges. You must create all JWT's on your server and not in the browser. This is because the JWT MAC Key used in JWT creation must only be known to you, Worldpay and Cardinal Commerce.

Best practice: We strongly recommend that you use a third-party library to create the JWT in its entirety.

JWT structure

JWT's consist of three parts (Header, Body and MAC). They are described below:

Header

The purpose of the header is to identify that the body is a JWT and to specify the message authentication algorithm. This is used to create the Message Authentication Code (MAC). The following algorithms are supported:

  • HS256 (HMAC with SHA256)
  • HS512 (HMAC with SHA512)

Example Header:

Copied!
{
  "typ":"JWT",
  "alg":"HS256"
}

Body

A JSON object that contains the claims (name-value pairs) being sent from one party to another. The body must only contain the claims below, adding additional claims results in 400 Bad Request response.

Claim NameM/ODescription
jtiMJWT Id - A unique identifier for this JWT. This field must be set to a random UUID each time a JWT is generated.
iatMIssued At - The epoch time (in seconds - 10 digits) of when the JWT was generated. Valid for two hours.
issMIssuer - An identifier of who is issuing the JWT. Use "5bd9e0e4444dce153428c940" in test. We will provide values for live.
expOExpiration - The numeric epoch time (in seconds - 10 digits) that the JWT should be considered expired. Anything over two hours in the future will be ignored.
OrgUnitIdMOrganisational Unit Id - An identity associated with your account. Use "5bd9b55e4444761ac0af1c80" in test. We will provide the values for live.

Example Body:

Copied!
{
  "jti": "69adc185-1748-4525-9ef9-43f259a1c2d6",
  "iat": 1548838855,
  "iss": "5bd9e0e4444dce153428c940",
  "exp": 1548838900,
  "OrgUnitId": "5bd9b55e4444761ac0af1c80"
}

MAC

A Base64url encoded hash value of the header and payload combined with a JWT MAC Key. This is used to verify that the contents of the JWT have not been tampered with. Authentication codes are verified by the consumer by recreating the MAC from the JWT header, body and JWT MAC Key.

AttributeDescription
JWT MAC KeyPass this as a string and not a number. Use fa2daee2-1fbb-45ff-4444-52805d5cd9e0 in test. We will provide the values for live.

Obtaining JWT

You must obtain a new JWT for each payment attempt. One way to get the JWT is by fetching it asynchronously from your server.

Example Javascript:

Copied!
async function getJwt() {
 const response = await fetch("[YOUR_ENDPOINT]")
   .catch(function(error) {
     alert("The server connection failed! Please try again later.")
     console.log('Error encountered:' + error);
     });
 const text = await response.text();

 return text;
}

The above function gets called in the payment form, when clicking the Pay button:

Copied!
<input id="payButton" type="button" value="Pay"
    onclick="getJwt().then((jwt) => { submitForm(event, document.getElementById('cardNumber').value, jwt) });"/>

DDC initiation

Create an invisible iframe on your payment page which submits the Bin (card number) and JWT to https://secure-test.worldpay.com/shopper/3ds/ddc.html (we will provide the live URL).

Example iframe body:

Copied!
<body onload='document.collectionForm.submit();'>
   <form id='collectionForm' name='collectionForm' method='POST' action='https://secure-test.worldpay.com/shopper/3ds/ddc.html'>
       <input type='hidden' name='Bin' value='4444333322221111'> 
       <input type='hidden' name='JWT' value='[SERVER-SIDE GENERATED JWT]'> 
   </form>
</body>

Full example of the function that performs the DDC call:

Copied!
function performDDC(bin, jwt) {
  let innerHtml =
        "<body onload='document.collectionForm.submit();'>" +
        "<form id='collectionForm' name='collectionForm' method='POST' action='https://secure-test.worldpay.com/shopper/3ds/ddc.html'>" +
        "<input type='hidden' name='Bin' value='" + bin + "'>" +
        "<input type='hidden' name='JWT' value='" + jwt + "'>" +
        "</form>" +
        "</body>";

  //set iframe properties
  var iframe = document.createElement("iframe");
  iframe.id = "ddcIframe";
  iframe.width = 0;
  iframe.height = 0;
  iframe.style.visibility = "hidden";
  iframe.style.display = "none";
  iframe.target = "_top";
  iframe.srcdoc = innerHtml;

  document.body.appendChild(iframe);
}

DDC Outcome

You are notified via a JavaScript postMessage that DDC has been completed. Your website must listen for this notification which will contain the following fields:

NameValue
MessageTypeprofile.completed
SessionIdUUID, not present or undefined
Statustrue or false
Copied!
{
    "MessageType": "profile.completed",
    "SessionId": "d3197c02-6f63-4ab2-801c-83633d097e32",
    "Status": true
}

Extract the SessionId and submit it with the payment details form as dfReferenceId. In case of failure, in this example, dfReferenceId will remain empty and the payment will downgrade to 3DS1. Alternatively you can retry DDC.

Example of capturing dfReferenceId:

Copied!
window.addEventListener("message", function(event) {
 var domain = extractDomain("https://secure-test.worldpay.com/shopper/3ds/ddc.html"); 

 if(event.origin === domain) {
   let data = JSON.parse(event.data);

   if (data && data.Status && data.SessionId) {
     document.getElementById('dfReferenceId').value = data.SessionId;
     } else {
       //take action
       //dfReferenceId will remain empty
     }
 }
 }, false);

Client Side Encryption (CSE)

This step tells you how you create the <encryptedData>, using the custom integration mode, you must add to your payment form.

Import the Worldpay CSE library

More Information about our CSE JavaScript library is available onGithub.

Set your public key

Copied!
window.onload = function() {
      Worldpay.setPublicKey('1#10001#bab7d41a4539d5f8e37116da62aa0175f5be8ca920'
                    + '78bfc21769ec1566bfd3d45cc29af09d701b1ae7645c1340d0'
                    + '0c5bc3a4c2b0b149e089b61f0aef476e4672bd82a90a8187a5'
                    + '2cbd57f3866810d01c4d1a88dfdb1021b1de220a157e9b5d49'
                    + '161c4311742885555f8edb3829c083c60be3b6beef3df62752'
                    + '270268a41ed83dafcc9638bc73a19cfce7f7d4a2f1d09c00bd'
                    + '8e00f5fa7c53f2d3155ad36d6f08c9c21a0491cc604e976de1'
                    + 'f5e93228dc4798a53fe4c53b61ea1355a668765471b3d3e2c1'
                    + '0260f2b0b0140fc6b6353c0666b25b9310958a3bf63dd7fd52'
                    + '2b724105ca6711d2e5fa2019cc42eb9223909542273fb5a179'
                    + '15d842f41f9399591715');
}

Implement an error handling function

The Worldpay CSE library performs some validations on all card data fields, which prevents invalid data to be submitted to your server.

Best Practice: This is highly recommended as unseen errors might stop the form from working.

Example of an error handling function:

Copied!
function errorHandler(errorCodes) {
    if(errorCodes.includes(101) || errorCodes.includes(103)) {
      //take action
    }
    //...
}

Complete the integration

Create a JSON object which holds the card data. The JSON object also calls the encryption function and passes the encrypted card data to your server.

Here's an example:

Copied!
function performCSE() {
  var sensitiveData = {
      cardNumber: document.getElementById('cardNumber').value,
      cvc: document.getElementById('cvcCode').value,
      expiryMonth: document.getElementById('expirationMonth').value,
      expiryYear: document.getElementById('expirationYear').value,
      cardHolderName: document.getElementById('cardHolderName').value
  };
  //sets fields to empty if no value is present
  for(var key in sensitiveData) {
    let value = sensitiveData[key];
    if(!value) {
      value = "";
    }
  }
  //performs encryption and allows to send encrypted data to your server
  document.getElementById('encryptedData').value = Worldpay.encrypt(sensitiveData, errorHandler);
}

Making it work together

The flow is, as follows:

  1. DDC is started (iframe is created and submitted)
  2. DDC result is expected - you may customise the timeout interval
  3. CSE is performed

If no errors occur, the payment form is submitted to the server.

Since JavaScript is not multithreaded, the CSE happens after a timeout interval. If no DDC response is received during that interval, the dfReferenceId will be submitted as empty in this example. The payment will therefore downgrade to 3DS1. You may also implement additional field validations.

Full example:

Copied!
function submitForm(event, bin, jwt) {
    //your validations here
    //perform DDC first - create the iframe and submit it (see above)
    performDDC(bin, jwt);
    window.setTimeout(function() {
        performCSE(); //also performs field validation
    //check for errors and prevent submission - use the Worldpay error handler or your own method
    if(!hasError) {
            document.getElementById('paymentDetailsForm').submit();
        } else {
            //take action - prevent submission
        }
    }, 4000); //4 second timeout - in the meantime the DDC event listener should catch the response
}

Initial Payment Request

Create theXML authorisation request.

Add the <CSE-DATA> tag under <paymentDetails>. This contains the <encryptedData> and <cardAddress>.

The 3DS Flex integration also requires some special elements in your payment request XML:

  • <additional3DSData> (mandatory)

  • <riskData> (increases the chance of a frictionless flow)

Further information on these elements is available in the3DS Flex guide.

Full initial payment request example:

Copied!
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE paymentService PUBLIC "-//Worldpay//DTD Worldpay PaymentService v1//EN"
    "http://dtd.worldpay.com/paymentService_v1.dtd" >
<paymentService version="1.4" merchantCode="YOUR_MERCHANT_CODE">
    <!--Enter your own merchant code-->
    <submit>
        <order orderCode="YOUR_ORDER_CODE">
            <!--Enter a unique order code each time-->
            <description>YOUR DESCRIPTION</description>
            <amount value="2000" currencyCode="EUR" exponent="2"/>
            <orderContent>
                <![CDATA[]]>
            </orderContent>
            <paymentDetails>
                <CSE-DATA>
                    <encryptedData>
                      eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiMSIsImNvbS53b3JsZHBheS5hcGlWZXJzaW9uIjoiMS4wIiwiY29tLndvcmxkcGF5LmxpYlZlcnNpb24iOiIxLjAuMSIsImNvbS53b3JsZHBheS5jaGFubmVsIjoiamF2YXNjcmlwdCJ9DaKoSziapSrotSTkbs9FAufAzq35DYC2T8QC-CQM9IPSt6KvT8c6etMiDq0AtSq3mfSlLXUogSccYdDFIRc3hXNr8aKO0IFN_XxUVrTRslZyF9cPueOEJFSbzzDapymfvclejWuo9rp6y8iNesuwql8-KQNGcfQRynL9yzMbrd-ZECShrLn5IMjAAFQYtwyBiFXk2xtEPvO2RQ0TJGbRTmAaZXy96cJT4f363AFk-7NtdZTK5xEQAifQfHHoXaGpw7xOmMnDUt121PAp5LHNrwLwGHKz6-CVJa6C7lMWynAc3E6VVLiZQw2YpIj8SS2big9bP-jGSGR1HcgzrCMaBA.46gvxZFZcHf6-s8t.ri4NV_sUrgav4fP4ZlNdUS3YlWzPHITP6rAL51wVM1lsTDBAok_JmAK1uWYXBhTKdHLs_Uk577d4yjlJ5Cy6_uh9lpAYo6_-gkLpwSduWeVOQ71VvoU7OT4RfhhfEngNctj6zjb0MjgIo7WXaWhKjw.sx0VhKfKKEb7OBRz2QKu7g
                  </encryptedData>
                    <cardAddress>
                        <address>
                            <address1>Worldpay</address1>
                            <address2>270-289 The Science Park</address2>
                            <address3>Milton Road</address3>
                            <postalCode>CB4 0WE</postalCode>
                            <city>Cambridge</city>
                            <countryCode>GB</countryCode>
                        </address>
                    </cardAddress>
                </CSE-DATA>
                <session shopperIPAddress="127.0.0.1" id="SESSION_ID"/>
                <!--Session id must be unique for each order-->
            </paymentDetails>
            <shopper>
                <shopperEmailAddress>jshopper@myprovider.com</shopperEmailAddress>
                <browser>
                    <acceptHeader>text/html</acceptHeader>
                    <userAgentHeader>Mozilla/5.0 ...</userAgentHeader>
                </browser>
            </shopper>
            <!-- Optional Risk Data -->
            <riskData>
        <authenticationRiskData authenticationMethod="localAccount">
          <authenticationTimestamp><date second="01" minute="02" hour="03" dayOfMonth="01" month="06" year="2019"/></authenticationTimestamp>
        </authenticationRiskData>
        <shopperAccountRiskData
          transactionsAttemptedLastDay="1"
          transactionsAttemptedLastYear="100"
          purchasesCompletedLastSixMonths="50"
          addCardAttemptsLastDay="1"
          previousSuspiciousActivity="true"
          shippingNameMatchesAccountName="true"
          shopperAccountAgeIndicator="lessThanThirtyDays"
          shopperAccountChangeIndicator="lessThanThirtyDays"
          shopperAccountPasswordChangeIndicator="noChange"
          shopperAccountShippingAddressUsageIndicator="thisTransaction"
          shopperAccountPaymentAccountIndicator="lessThanThirtyDays">          
          <shopperAccountCreationDate><date dayOfMonth="01" month="02" year="2003"/></shopperAccountCreationDate>
          <shopperAccountModificationDate><date dayOfMonth="02" month="03" year="2004"/></shopperAccountModificationDate>
          <shopperAccountPasswordChangeDate><date dayOfMonth="03" month="04" year="2005"/></shopperAccountPasswordChangeDate>
          <shopperAccountShippingAddressFirstUseDate><date dayOfMonth="04" month="05" year="2006"/></shopperAccountShippingAddressFirstUseDate> 
          <shopperAccountPaymentAccountFirstUseDate><date dayOfMonth="05" month="06" year="2007"/></shopperAccountPaymentAccountFirstUseDate>
        </shopperAccountRiskData>
        <transactionRiskData
          shippingMethod="shipToBillingAddress"
          deliveryTimeframe="overnightShipping"
          deliveryEmailAddress="sp@worldpay.com"
          reorderingPreviousPurchases="true"
          preOrderPurchase="false"
          giftCardCount="1">
        <transactionRiskDataGiftCardAmount><amount value="1" currencyCode="EUR" exponent="2"/></transactionRiskDataGiftCardAmount>
          <transactionRiskDataPreOrderDate><date dayOfMonth="06" month="07" year="2008"/></transactionRiskDataPreOrderDate>
        </transactionRiskData>
      </riskData>

      <!-- Additional 3DS data that you must provide to us -->
         <additional3DSData
           dfReferenceId="1f1154b7-620d-4654-801b-893b5bb22db1"
           challengeWindowSize="390x400"
           challengePreference="challengeMandated"
       />
    </order>
  </submit>
  </paymentService>

Next Steps

For the remainder of the authentication flow go to our3DS Flex guide.