1.1.0 • Published 5 months ago

@robtimus/connect-client-sdk v1.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

Worldline Connect client SDK

npm Build Status Quality Gate Status Coverage Known Vulnerabilities

An SDK for the Worldline Connect Client API. It's based on the official SDK, but is mostly written from scratch, and provides some improvements:

  • No hard requirement on browser specifics. Although this is the default, with only a little work it's possible to use the SDK in React Native apps.
  • No custom code for functionality most modern browsers support like Base64 encoding/decoding.
  • Proper promises.
  • HTTP calls use the Fetch API if available, with XMLHttpRequest available as fallback.
  • Native cryptography if available, through the Web Crypto API. node-forge can still be used as fallback.
  • No deprecated legacy code.
  • More support for creating Apple Pay and Google Pay payments.

Installation

Install this SDK using your preferred Node.js package manager like npm, yarn or pnpm. For instance:

npm i @robtimus/connect-client-sdk

Packaging

The SDK's default packaging is as separate ES modules. This can be used by most bundlers, but also in non-browser environments. The default export contains the most important types:

import { PaymentContext, PaymentRequest, Session, SessionDetails } from "@robtimus/connect-client-sdk";

Usage Universal Module Definition (UMD)

The SDK is available under global namespace connectClientSdk, and can be imported in the following way:

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <script src="./node_modules/@robtimus/connect-client-sdk/dist/connect-client-sdk.umd.js"></script>
  </head>
  <body>
    <script>
      const session = new connectClientSdk.Session({
        ...
      }, {
        ...
      })
    </script>
  </body>
</html>

Getting started

  1. Use the Worldline Connect Server APi's Create session call to create a session.

  2. Create a SessionDetails object using the Create session response:

const sessionDetails: SessionDetails = {
  assetUrl: "https://payment.pay1.secured-by-ingenico.com/",
  clientApiUrl: "https://eu.api-ingenico.com/client",
  clientSessionId: "f084372052fb47d9a766ec35bfa0e6bd",
  customerId: "9991-0b67467b30df4c6d8649c8adc568fd0f",
};

In JavaScript, the session response can be used as-is. In TypeScript you need to either remove the invalidTokens and region properties (if present), or cast the response to SessionDetails.

  1. Create a PaymentContext object with the necessary values (more properties can be added as necessary):
const paymentContext: PaymentContext = {
  amountOfMoney: {
    amount: 1000,
    currencyCode: "EUR",
  },
  countryCode: "NL",
};
  1. Create a Session object with the created SessionDetails and PaymentContext objects:
const session = new Session(sessionDetails, paymentContext);

Unlike the official SDK, this SDK requires the payment context to be specified up front. It can be updated using session.updatePaymentContext. This accepts a partial PaymentContext that will be merged into the session's payment context. This allows you to only specify the changes you want to make.

  1. Use the Session object to retrieve the available payment products, payment product groups, and their accounts on file. Display these, and let your customer select one:
const paymentItems = await session.getBasicPaymentItems();
// Display the contents of paymentItems.paymentItems (the payment products and payment product groups)
// and paymentItems.accountsOnFile
  1. When a payment product or payment product group is selected, use the Session object to retrieve more information about the payment product or payment product group. This contains the fields for the product. Display these fields and let your customer fill them in:
const paymentProduct = await session.getPaymentProduct(1);
// or const paymentProductGroup = await session.getPaymentProductGroup("cards");
// Display the contents of paymentProduct.fields or paymentProductGroup.fields
  1. Create an instance of PaymentRequest, set its payment product, and store the value entered for each field:
const paymentRequest = new PaymentRequest();
paymentRequest.setPaymentProduct(paymentProduct);
paymentRequest.setValue("cardNumber", "4567 3500 0042 7977");
paymentRequest.setValue("cvv", "123");
paymentRequest.setValue("expiryDate", "12/99");
paymentRequest.setValue("cardholderName", "Wile E. Coyote");

The values can be masked (as shown above), as long as the mask matches the field's mask. The SDK will automatically remove these masks when needed.

  1. Validate the PaymentRequest object:
const result = paymentRequest.validate();
if (!result.valid) {
  // result.errors contains each validation error, e.g. { fieldId: "cardNumber", ruleId: "luhn" }
  // or { fieldId: "cvv", ruleId: "required" }
  // These can be be used to display error messages to your customer
}
  1. Encrypt the contents of the PaymentRequest object:
const encryptor = session.getEncryptor();
const payload = encryptor.encrypt(paymentRequest);
  1. Send the payload to your server, where it can be used for the encryptedCustomerInput property of a Create payment call.

Extended usage

IIN details

When using the cards payment product group, it's important to know which payment product a card belongs to. This can be done by retrieving the IIN details, and checking the status property:

const iinDetails = await session.getIINDetails("4567 35");
switch (iinDetails.status) {
  case "SUPPORTED":
    // The card is known and allowed within the current payment context.
    // iinDetails.isAllowedInContext is true, iinDetails.paymentProductId and iinDetails.countryCode
    // are available.
    // If the card has multiple brands, iinDetails.coBrands contains the paymentProductId for each of them,
    // and an isAllowedInContext flag that determines whether or not the co-brand is allowed for the
    // current context.
    // To render the brands, session.getPaymentProductDisplayHints can be called with the paymentProductId
    // field of each support cobrand for which isAllowedInContext is true.
    break;
  case "NOT_ALLOWED":
    // The card is known but not allowed within the current payment context.
    // Like SUPPORTED, but iinDetails.isAllowedInContext is false.
    break;
  case "NOT_ENOUGH_DIGITS":
    // Fewever than 6 digits were provided. No additional properties are available.
    break;
  case "UNKNOWN":
    // The card is not known.
    // iinDetails.errorId and iinDetails.errors come from the Worldline Connect Client API error response.
    break
}

Payment product specifics

Apple Pay

if Apple Pay (302) is one of your available payment products, you need to provide some more information in the PaymentContext to be able to use it:

const paymentContext: PaymentContext = {
  ...
  paymentProductSpecificInputs: {
    applePay: {
      merchantName: "Your name",
      merchantCountryCode: "Your optional 2-letter ISO country code;\
                            the acquirer country as returned by the Worldline Connect Client API\
                            will be used if available, otherwise this value if specified,\
                            otherwise the country code from the payment context",
      lineItem: "Optional line item; if not set the merchant name will be used",
    }
  }
};

You can then use the following code to set a PaymentRequest's value for Apple Pay's encryptedPaymentData field:

const applePayProduct = await session.getPaymentProduct(302);
// or const applePayProduct = paymentProducts.getPaymentProduct(302);
// or const applePayProduct = paymentItems.getPaymentItem(302);
const applePayHelper = await session.ApplePay(applePayProduct);
const paymentData = await applePayHelper.createPayment();

paymentRequest.setValue("encryptedPaymentData", JSON.stringify(paymentData.paymentData));

If the browser does not support Apple Pay, the SDK will filter it out of the available payment products.

Google Pay

If Google Pay (320) is one of your available payment products, you need to provide some more information in the PaymentContext to be able to use it:

const paymentContext: PaymentContext = {
  ...
  paymentProductSpecificInputs: {
    googlePay: {
      connectMerchantId: "Your Connect merchant id",
      googlePayMerchantId: "Your Google Pay merchant id",
      transactionCountryCode: "ISO 3166-1 alpha-2 country code for the country where the transaction\
                               will be completed/processed;\
                               the acquirer country as returned by the Worldline Connect Client API\
                               will be used if available, otherwise this value if specified,\
                               otherwise the country code from the payment context",
      merchantName: "Your optional user visible merchant name; if not set the Business name in your\
                     Google Pay Developer Profile will be used",
      environment: "PRODUCTION", // or "TEST" for test purposes
    }
  }
};

You can then use the following code to set a PaymentRequest's value for Google Pay's encryptedPaymentData field:

const googlePayProduct = await session.getPaymentProduct(320);
// or const googlePayProduct = paymentProducts.getPaymentProduct(320);
// or const googlePayProduct = paymentItems.getPaymentItem(320);
const googlePayHelper = await session.GooglePay(googlePayProduct);
const paymentData = await googlePayHelper.createPayment();

paymentRequest.setValue("encryptedPaymentData", paymentData.paymentMethodData.tokenizationData.token);

The googlePayHelper can be used before initializing the payment for two purposes: 1. googlePayHelper.prefetchPaymentData() prefetches configuration to improve createPayment execution time on later user interaction. 2. googlePayHelper.createButton({ ... }) creates a button that you can add to your page (see https://developers.google.com/pay/api/web/guides/tutorial#add-button). Note that the allowedPaymentMethods will be set for you, you only need to provide the onClick handler and display properties.

You need to load an additional JavaScript file (see https://developers.google.com/pay/api/web/guides/tutorial#js-load). If you don't, the SDK will filter Google Pay out of the available payment products.

Bancontact

By default, retrieving the Bancontact (3012) product will not return the fields of the basic flow. You can force these fields to be returned by providing some more information in the PaymentContext:

const paymentContext: PaymentContext = {
  ...
  paymentProductSpecificInputs: {
    bancontact: {
      forceBasicFlow: true,
    }
  }
};

See the details of the forceBasicFlow query parameter of the Get payment product call for more information.

Masking field values

To help in formatting field values like credit cards or expiry dates, PaymentProductField provides functions applyMask, applyWildcardMask and removeMask to apply and remove masks. For example:

const cardNumberField = paymentProduct.getField("cardNumber");
const value = "4567350000427977";

const maskedValue = cardNumberField.applyMask(value).formattedValue; // 4567 3500 0042 7977

const unmaskedValue = cardNumberField.removeMask(maskedValue); // 4567350000427977

Accounts on file (tokens)

Accounts on file are available from instances of BasicPaymentItem / PaymentItem, BasicPaymentProduct / PaymentProduct and BasicPaymentProductGroup / PaymentProductGroup. They are also available from instances of BasicPaymentItems, BasicPaymentProducts and BasicPaymentProductGroups; these contain the accounts on file of each of their elements.

An account on file can be rendered in the list of available accounts on file using the label template elements of its display hints in combination with the account on file attributes:

const accountOnFile = ...;
const displayValue = accountOnFile.displayHints.labelTemplate
  .map((e) => accountOnFile.getAttributeDisplayValue(e.attributeKey))
  .join(" ");
// or equivalent:
const label = accountOnFile.getLabel();

To render an account on file's detail page, fetch its matching payment product and display it mostly as usual. However, some fields should not be editable. AccountOnFile has method isReadOnlyAttribute that can be used. For instance:

const paymentProduct = await session.getPaymentProduct(accountOnFile.paymentProductId);
for (const field of paymentProduct.fields) {
  if (accountOnFile.isReadOnlyAttribute(field.id)) {
    // render accountOnFile.getAttributeDisplayValue(field.id) as text or a disabled input element
  } else {
    const attribute = accountOnFile.findAttribute(field.id);
    if (attribute) {
      // render field with attribute.value as initial value
    } else {
      // render field as usual
    }
  }
}

When creating a PaymentRequest, make sure to set not only the payment product but also the account on file:

const paymentRequest = new PaymentRequest();
paymentRequest.setPaymentProduct(paymentProduct);
paymentRequest.setAccountOnFile(accountOnFile);
// Read-only fields like the cardNumber should not be set
// The cvv is usually not stored in an account on file
// The expiryDate is usually editable because it changes every time a new card is issued
paymentRequest.setValue("cvv", "123");
paymentRequest.setValue("expiryDate", "12/99");

Server-side rendering support

In case you perform server-side rendering, and the payment product or payment product group is already available, it's possible to set this on a Session object:

session.setProvidedPaymentItem({
  id: 1,
  ...
});

Whenever this payment product or payment product group is retrieved, the Worldline Connect Client API will not be queried, but the provided value will be returned instead.

The value should be provided as returned by the Worldline Connect Client API or the Worldline Connect Server API. The SDK will convert this as necessary.

Non-browser support

The official SDK cannot be used outside of browsers, because it relies on browser mechanics like XMLHttpRequest and window.\ By default this SDK also doesn't work outside of browsers. However, it's possible to replace the browser specifics with a custom implementation of Device. This consists of:

  • An HttpClient. fetchHttpClient can be used, or a custom implementation can be created.
  • DeviceInformation that describes some device-specifics. This is used for metadata that's sent to the Worldline Connect Client API, as well as encrypted customer input.
  • Optionally, an ApplePayClient and GooglePayClient. If either one is not available, the matching payment product will be filtered out.

When you've created a custom Device, provide it when creating a Session object:

const device = ...;
const session = new Session(sessionDetails, paymentContext, device);

API

The API can be found here.

Differences from the Worldline Connect Client API

Requirements

Browsers

The following are the minimum supported browsers:

BrowserMin version
Chrome74+
Edge79+
Safari14.1+
Firefox90+
Opera62+
Chrome for Android113+
Safari on iOS14.5+
Opera Mobile73+
Android Browser113+
Firefox for Android113+

Internet Explorer and older versions of these browser can become supported by using a polyfill like core-js.

If the Web Crypto API is not available, it's possible to use node-forge instead. For instance:

import { forgeCryptoEngine } from "@robtimus/connect-client-sdk/dist/ext/impl/crypto/forge";
import { webCryptoCryptoEngine } from "@robtimus/connect-client-sdk/dist/ext/impl/crypto/WebCrypto";

Session.defaultCryptoEngine = webCryptoCryptoEngine ?? forgeCryptoEngine;

// create session as usual

When using UMD, this is done automatically when using connect-client-sdk.forge.umd.js instead of connect-client-sdk.umd.js:

<script src="./node_modules/@robtimus/connect-client-sdk/dist/connect-client-sdk.forge.umd.js"></script>

Node.js

Node.js 16 or higher is required.

Building the repository

From the root of the project install all dependencies, then compile the code:

npm ci
npm run build
# optional:
npm run typedoc

Testing

There are three types of tests:

  1. Unit tests. These will work out-of-the-box.\ Run these tests as follows:

    npm run test
  2. Integration tests. Before you can run these, you first need to copy file test/config.json.dist to test/config.json and replace all values as needed.\ Run these tests as follows:

    npm run test:integration
  3. Selenium (in-browser) tests. Before you can run these, you first need to copy file test/config.json.dist to test/config.json and replace all values as needed.\ Run these tests as follows:

    npm run test:selenium

You can also run all these types of tests together as follows:

npm run test:all
1.1.0

5 months ago

1.0.0

7 months ago