@robtimus/connect-client-sdk
v1.1.0
Published
An SDK for the Worldline Connect Client API
Downloads
1
Readme
Worldline Connect client SDK
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
Use the Worldline Connect Server APi's Create session call to create a session.
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
andregion
properties (if present), or cast the response toSessionDetails
.
- 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",
};
- Create a Session object with the created
SessionDetails
andPaymentContext
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 partialPaymentContext
that will be merged into the session's payment context. This allows you to only specify the changes you want to make.
- 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
- 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
- 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.
- 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
}
- Encrypt the contents of the
PaymentRequest
object:
const encryptor = session.getEncryptor();
const payload = encryptor.encrypt(paymentRequest);
- 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:
googlePayHelper.prefetchPaymentData()
prefetches configuration to improvecreatePayment
execution time on later user interaction.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 theallowedPaymentMethods
will be set for you, you only need to provide theonClick
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:
| Browser | Min version | |---------------------|-------------| | Chrome | 74+ | | Edge | 79+ | | Safari | 14.1+ | | Firefox | 90+ | | Opera | 62+ | | Chrome for Android | 113+ | | Safari on iOS | 14.5+ | | Opera Mobile | 73+ | | Android Browser | 113+ | | Firefox for Android | 113+ |
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:
Unit tests. These will work out-of-the-box.
Run these tests as follows:npm run test
Integration tests. Before you can run these, you first need to copy file
test/config.json.dist
totest/config.json
and replace all values as needed.
Run these tests as follows:npm run test:integration
Selenium (in-browser) tests. Before you can run these, you first need to copy file
test/config.json.dist
totest/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