Take encrypted server-to-server 3D Secure payments

Overview

The OnlinePay eCommerce API supports server-to-server payments, allowing merchants to process 3D Secure (3DS) transactions without requiring a client-side integration. This solution uses the verifone.js script, which is used to encrypt card details before they are sent to the API, as well as the Cardinal Commerce 3D Secure service to handle the 3D Secure authentication process via the songbird.js library.

The verifone.js library is used to capture and encrypt card details on your website. This allows you to securely capture card details without the card details touching your server, reducing the complexity of PCI compliance, and providing seamless integration into your existing website checkout pages.

CardinalCommerce's songbird.js is a JavaScript library used to facilitate 3D Secure (3DS) authentication in online payments. The songbird.js library is loaded on the client-side (browser), and is responsible for initialising the 3D Secure process using a JWT (JSON Web Token), obtained from your 3DS Contract ID. It handles the authentication flow, including challenge prompts when required by the card issuer, and returns the authentication result to your backend server for processing.

Refer to the following recipe in addition to the complete documentation for more information on how to implement server-to-server 3D Secure payments:

Runtime Execution

The following steps must be executed in sequence during an actual payment transaction:

  1. Server-side: Generate JWT token from Verifone 3DS API (/oidc/3ds-service/v2/jwt/create).
  2. Client-side: Initialise 3D Secure with JWT token from server.
  3. Client-side: Collect session ID from setupComplete event.
  4. Client-side: Encrypt card details with verifone.getEncryptedCardDetails().
  5. Server-side: Perform 3DS lookup with encrypted card data and session ID (/oidc/3ds-service/v2/lookup).
  6. Client-side: Handle authentication challenge if required (pares_status = "C").
  7. Server-side: Process final payment with 3DS authentication data (/oidc/api/v2/transactions/card).

⚠️

The runtime execution steps have strict dependencies. For example, the 3DS lookup requires both the encrypted card details (from client) and the session ID (from Cardinal setup), while the final payment requires authentication data from the 3DS lookup process.

3D Secure Implementation Process

The complete 3D Secure payment flow consists of steps that must be implemented in a specific order to ensure proper functionality.

Client-side and server-side preparations can be done in parallel, but the runtime execution flow must follow the order outlined above.

Client-side Setup

The following configuration elements are required to set up the client-side for server-to-server 3D Secure payments:

  1. Set up client-side with required JavaScript libraries
  2. Configure the 3D Secure script
  3. Set up event listeners
  4. Initialise 3D Secure with a JWT token
  5. Implement card number detection

Server-side Setup

  1. Encrypt card details with Verifone.js
  2. Create a 3DS lookup endpoint
  3. Create a payment processing endpoint to complete the payment

Client-side Setup

1. Set up client-side with required JavaScript libraries

To capture and encrypt card details using verifone.js, you need to include the verifone.js library in the head of your web page used to capture card details. You also need to include the Cardinal Commerce songbird.js library to handle the 3D Secure authentication process.

<head>
  <script src="https://au.jsclient.verifone.cloud/verifone.js"></script>
  <script src="https://songbird.cardinalcommerce.com/edge/v1/songbird.js"></script>
</head>

2. Configure the 3D Secure script

Configure the 3D Secure script using the Cardinal.configure() method. This sets up the Cardinal Commerce library with appropriate timeout and retry settings:

Cardinal.configure({
  timeout: 6000,
  maxRequestRetries: 3,
  logging: { level: "verbose" }
});

3. Set up event listeners

Set up event listeners for Cardinal Commerce events:

Cardinal.on("payments.setupComplete", function(setupCompleteData) {
  sessionId = setupCompleteData.sessionId;
  document.getElementById("encryptBtn").disabled = false;
});

Cardinal.on("payments.validated", function(data, jwt) {
  switch(data.ActionCode) {
    case "SUCCESS":
      processPayment(data.Payment.ExtendedData);
      break;
    case "NOACTION":
      // Proceed with payment without 3DS
      break;
    case "FAILURE":
    case "ERROR":
      alert('Authentication failed. Please try again.');
      break;
  }
});

4. Initialise 3D Secure with JWT token

Initialise the Cardinal Commerce 3D Secure with a JWT token obtained from the 3D Secure API using the /oidc/3ds-service/v2/jwt/create endpoint.
This JWT token is required to start the 3D Secure authentication process.

async function initializeThreeDS() {
  try {
    const tokenData = await getJWTFromServer();
    Cardinal.setup("init", { jwt: tokenData.jwt });
  } catch (error) {
    console.error("Failed to initialise 3DS:", error);
  }
}

⚠️

The getJWTFromServer() function should be implemented to fetch the JWT token from your server-side endpoint that generates the token using the /oidc/3ds-service/v2/jwt/create endpoint. See 3D Secure API documentation for more details.

async function getJWTFromServer() {
  const response = await fetch("/api/auth/jwt-token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      threeds_contract_id: "your_3ds_contract_id", // Replace with your actual 3DS contract ID
    }),
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  const result = await response.json();
  return result.jwt;
}

5. Implement card number detection

Add the Cardinal field decorator to your card number input:

<input type="text" id="cardNumber" data-cardinal-field="AccountNumber" />

Encrypt card details with verifone.js

Capture and encrypt card using the verifone.getEncryptedCardDetails() method. This method requires the card details and your public Secure Card Capture key to encrypt the card data.

const encrypt = async () => {
  try {
    const publicKey = "LS0tLS1CRUd........."; // Replace with your actual Secure Card Capture key
    const card = {
      cardNumber: document.getElementById("cardNumber").value,
      expiryMonth: document.getElementById("expiryMonth").value,
      expiryYear: document.getElementById("expiryYear").value,
      cvv: document.getElementById("cardCvv").value,
    };

    console.log("Encrypting card with Verifone.js");
    const result = await verifone.getEncryptedCardDetails(
      card,
      publicKey
    );

    // Store the encrypted card details in a variable for later use:
    paymentEncryptedCard = result.encryptedCard;

    await performThreeDSLookup(result.encryptedCard);
  } catch (error) {
    console.error("Encryption failed:", error);
    alert(`Card encryption failed: ${error.message}`);
  }
};

ℹ️

verifone.encryptCard returns a Promise. Not all browsers support async-await, which you should consider in your implementation.

Perform 3DS lookup with encrypted card data

Send the encrypted card details and session ID to your server for 3DS lookup. Your server-side logic should be configured to handle the 3DS lookup to the 3D Secure API using the /oidc/3ds-service/v2/lookup endpoint. This endpoint requires the encrypted card details and session ID to perform the lookup.

async function performThreeDSLookup(encryptedCard) {
  const lookupData = {
    amount: 1000,
    currency_code: "AUD",
    encrypted_card: encryptedCard,
    device_info_id: sessionId,
    threeds_contract_id: "your_3ds_contract_id"
  };

  // Send the lookup data to your server
  const response = await fetch('/api/3ds-lookup', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(lookupData)
  });
  
  const result = await response.json();
  handleThreeDSResult(result);
};

Handle authentication challenge

Handle the 3DS authentication challenge if required:

function handleThreeDSResult(result) {
  if (result.pares_status === "C") {
    Cardinal.continue("cca", {
      AcsUrl: result.acs_url,
      Payload: result.payload
    },{
      OrderDetails: {
        TransactionId: result.transaction_id,
      }
    });
  } else if (result.pares_status === "Y") {
    // Proceed to payment
    processPayment(result);
  } else {
    console.log("⚠️ 3DS status unclear:", result.pares_status);
    alert(`3DS completed with status: ${result.pares_status}`);
  }
};

Process final payment with 3DS authentication data

When the 3DS authentication is successful, you can call the processPayment function with the 3DS data returned from the lookup and the encrypted card details.

⚠️

Your server-side logic should be configured to handle the final payment processing using the /oidc/api/v2/transactions/card endpoint.

This endpoint requires the encrypted card details, session ID, and 3DS authentication data to process the payment.

async function processPayment(threeDSData) {
  try {
    const paymentData = {
      amount: 1000,
      currency_code: "AUD",
      encrypted_card: paymentEncryptedCard,
      device_info_id: sessionId,
      threeds_contract_id: "your_3ds_contract_id",
      three_ds_data: threeDSData
    };
    // Send the payment data to your server
    const response = await fetch('/api/process-payment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(paymentData)
    });

    const result = await response.json();
    if (!response.ok || !result.success) {
      throw new Error(
      `Payment failed: ${result.message || "Unknown error"}`
      );
    }
  } catch (error) {
    console.error("Payment processing failed:", error);
    alert(`❌ Payment Failed!\n\n${error.message}`);
  }
};

See the Secure Card Capture Key documentation for more information on how to obtain your Secure Card Capture key.

See the eCommerce API documentation for more information on how to transact using the Secure Card Capture with the eCommerce API.



St. George BankSA Bank of Melbourne

This information is a general statement for information purposes only and should only be used as a guide. While all care has been taken in preparation of this document, no member of the Westpac Group, nor any of their employees or directors gives any warranty of accuracy or reliability nor accepts any liability in any other way, including by reason of negligence for any errors or omissions contained herein, to the extent permitted by law. Unless otherwise specified, the products and services described are available only in Australia.

© St.George, Bank of Melbourne and BankSA – Divisions of Westpac Banking Corporation ABN 33 007 457 141 AFSL and Australian credit licence 233714.