Embedded payments (Web Payment Module)
Embedded payments are a form of external payment that allows a third-party payment user interface (UI) to be rendered from within a client application.
Scripts for rendering an embedded payment UI are supplied by the external payment provider via Web Payment Module. Vista's systems do not receive the full card details from the embedded payment UI, which is handled by the external payment provider, removing related security concerns.
Note:
- Only a single embedded payment can be made against an order.
- An embedded payment can be combined with a member balance payment and/or one or more gift card payments.
- An embedded payment cannot be combined with a redirect payment .
- An embedded payment can be created before other payments have been added, but must be completed as the final stage of an order, after any other payments have been added.
- The order will be automatically completed after the embedded payment is completed.
NOTE
For access to React-based embedded payment UI reference code, talk to your local Vista representative.
Embedded payment stages
The embedded payment API flow includes a combination of Digital Platform, JavaScript, and Web Payment Module interaction.
Create embedded payment
An embedded payment can be initiated for an in-progress order by calling the CreateEmbeddedPayment endpoint with the webPaymentMethodId
of the configured embedded payment method.
{
"webPaymentMethodId": 2,
"redirectReturnUrl": "https://example.com/order/payment",
"languageTag": null
}
Note:
- The order must have a non-zero remaining balance to pay. For orders with zero remaining balance to pay, use the CreateOrderCompletion endpoint to complete the order directly.
- For standard and gift shop orders, the customer's details must be set prior to creating an embedded payment.
-
If the
webPaymentMethodId
is unknown, use the GetWebPaymentMethods endpoint to load the list of available payment methods for the order. See Retrieving configured payment methods for more info. -
Set the
redirectReturnUrl
to the client application's payment page (i.e.,window.location.href
) so that the payment connector will return to the application after redirecting for Strong Customer Authentication (e.g., 3DS), if required. -
For multilingual applications, set the
languageTag
to the patron's preferred language, e.g.,"en-GB"
. - If a gift card payment or member balance payment is made after an embedded payment is created, the embedded payment must be re-created, and the embedded payment UI must be re-rendered.
A clientData
object will be returned containing the details required to load and render the embedded payment UI:
{
"embeddedPayment": {
"id": "string",
"type": "Embedded",
"value": 0.1,
"status": "Pending"
},
"clientData": {
"connectorParametersJson": "string",
"completePaymentUrl": "https://example.com",
"sdkScriptUrls": ["string"],
"connectorScriptUrls": ["string"],
"connectorFunctionName": "string",
"isPaymentButtonIncluded": true
},
"updatedOrderExpiresAt": "2019-08-24T14:15:22Z"
}
NOTE
If your application automatically cancels expired orders, the updatedOrderExpiresAt
value should be used to update the order's expiry time.
Your application should also track that an embedded payment has been created to avoid cancelling the order prematurely.
Load embedded payment UI scripts
The client application must load the embedded payment scripts using the sdkScriptUrls
and connectorScriptUrls
values returned in the clientData
object.
You may load these scripts by adding <script>
tags to the <head>
of the HTML document, for example:
/**
* Loads the scripts required to render an embedded payment UI.
*
* @param clientData The `clientData` object returned by the CreateEmbeddedPayment endpoint.
* @returns A promise that resolves when all scripts have been loaded
*/
async function loadEmbeddedPaymentScripts(clientData) {
const scriptUrls = [
...clientData.sdkScriptUrls,
...clientData.connectorScriptUrls,
];
return await Promise.all(
scriptUrls.map((scriptUrl) => loadScript(scriptUrl)),
);
}
/**
* Cache each loadScript promise to ensure that scripts aren't unnecessarily re-loaded.
*/
const loadScriptPromiseCache = new Map();
/**
* Load a script into the DOM via a promise.
*
* @param url The URL of the script to load.
* @returns A promise that resolves when the script has been loaded.
*/
async function loadScript(url) {
// If the script is currently loading, return the cached promise.
const cachedPromise = loadScriptPromiseCache.get(url);
if (cachedPromise) {
return cachedPromise;
}
// Skip if the script has already been loaded.
if (document.querySelector(`script[src="${url}"]`)) {
return;
}
const head = document.head || document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.src = url;
script.type = 'text/javascript';
const promise = new Promise((resolve, reject) => {
script.onload = () => resolve();
script.onerror = () => reject();
});
head.appendChild(script);
loadScriptPromiseCache.set(url, promise);
promise.finally(() => {
loadScriptPromiseCache.delete(url);
});
return promise;
}
IMPORTANT
The client application must await
the loadEmbeddedPaymentScripts
function before rendering the embedded payment UI to ensure that all required scripts have been loaded.
Render embedded payment UI
After all payment scripts have been loaded, use the clientData
object returned by the CreateEmbeddedPayment endpoint to render the embedded payment UI in the client application.
First, create a container element in the client application where the embedded payment UI will be rendered:
<html>
<body>
<div id="embedded-payment"></div>
</body>
</html>
Then, render the embedded payment UI using the clientData
and a reference to the container element:
/**
* Renders the UI for an embedded payment.
*
* @param clientData The `clientData` object returned by the CreateEmbeddedPayment endpoint.
* @param element A reference to the DOM element that the payment UI should be rendered into.
* @param callbacks Callbacks for handling events from the payment UI.
* @returns A reference to the rendered payment UI with `unmount()` and `startPayment()` methods.
*/
function renderEmbeddedPaymentUI(clientData, element, callbacks) {
const serverParams = JSON.parse(clientData.connectorParametersJson);
const clientParams = {
// A reference to the DOM element for the payment UI.
container: element,
// Callbacks for handling events from the payment UI.
onPaymentStarted: () => callbacks.onPaymentStarted(),
onPaymentTokenReceived: (token) =>
callbacks.onPaymentTokenReceived(clientData.completePaymentUrl, token),
onPaymentMethodDeclined: (errorMessage) =>
callbacks.onPaymentMethodDeclined(errorMessage),
onPaymentButtonStateChanged: ({ isDisabled }) =>
callbacks.onPaymentButtonStateChanged({ isDisabled }),
onError: (errorMessage) => callbacks.onError(errorMessage),
};
// Return a reference to the rendered payment UI.
// Use the `unmount()` method to teardown the UI.
// Use the `startPayment()` method to start when using a custom payment button.
return window.vista.payments.connector[clientData.connectorFunctionName](
serverParams,
clientParams,
);
}
const embeddedPaymentUI = renderEmbeddedPaymentUI(
clientData,
document.getElementById('embedded-payment'),
{
onPaymentStarted: () => undefined, // store 'payment started' state
onPaymentTokenReceived: postPaymentTokenToWPM, // POST the payment token to Web Payment Module
onPaymentMethodDeclined: (errorMessage) => undefined, // handle payment cancelled or declined
onPaymentButtonStateChanged: ({ isDisabled }) => undefined, // update custom payment button state
onError: (error) => undefined, // handle unexpected error
},
);
Custom payment button
Some payment connectors do not include a 'pay now' button, as indicated by the clientData.isPaymentButtonIncluded
property returned by the CreateEmbeddedPayment endpoint.
If the payment connector doesn't include a 'pay now' button, the client application should render a custom button that calls the startPayment()
method of the payment UI
when clicked:
<html>
<body>
<div id="embedded-payment"></div>
<button onclick="onPaymentButtonClick()">Pay now</button>
<script>
// Render the embedded payment UI.
const embeddedPaymentUI = renderEmbeddedPaymentUI(...);
// Start the embedded payment when the custom button is clicked.
function onPaymentButtonClick() {
embeddedPaymentUI.startPayment();
}
</script>
</body>
</html>
Callback methods
The callbacks
object passed to the renderEmbeddedPaymentUI
function includes several callback methods for handling events from the embedded payment UI:
onPaymentStarted
The onPaymentStarted
callback is called when the payment has been started by the patron.
IMPORTANT
The client application should track that an embedded payment has been started when the onPaymentStarted
callback is called to
confirm payment completion after redirection for Strong Customer Authentication (e.g., 3DS).
onPaymentTokenReceived
The onPaymentTokenReceived
callback is called when the payment token has been created successfully.
The client application must send the token to Web Payment Module to complete the payment.
onPaymentMethodDeclined
The onPaymentMethodDeclined
callback is called when the payment was cancelled or declined by the connector.
The client application should display an appropriate message with a 'try again' button that restarts the payment.
onPaymentButtonStateChanged
The onPaymentButtonStateChanged
callback is called when the payment button state has changed.
The client application should update the disabled
state of the custom payment button based on the isDisabled
prop.
onError
The onError
callback is called when an unexpected error has occurred.
The client application should display an appropriate error message or restart the payment.
Teardown
The embedded payment UI can be torn down by calling the unmount()
method of the returned payment UI:
embeddedPaymentUI.unmount();
The embedded payment UI should be torn down when:
- The payment has been completed successfully.
- The payment has been declined by the connector.
- An unexpected error has occurred.
Patron completes or cancels payment
The rendered embedded payment UI will allow the patron to complete or cancel the payment.
The onPaymentTokenReceived
callback will be called when the patron successfully creates a payment token.
The client application must send the token to Web Payment Module to complete the payment.
The onPaymentMethodDeclined
callback will be called if the payment is cancelled by the patron or declined by the connector.
The client application should display an appropriate message with a 'try again' button that restarts the payment.
Send token to Web Payment Module
Once the onPaymentTokenReceived
callback is called, the client application must POST
the payment token to Web Payment Module to complete the payment:
/**
* Posts a payment token to Web Payment Module (WPM) to complete an embedded payment.
*
* @param completePaymentUrl The `clientData.completePaymentUrl` returned by the CreateEmbeddedPayment endpoint.
* @param token The token received from the payment UI.
*/
async function postPaymentTokenToWPM(completePaymentUrl, token) {
try {
const response = await fetch(completePaymentUrl, {
method: 'POST',
body: token,
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
}),
});
// Redirect required for Strong Customer Authentication (e.g., 3DS).
if (response.status === 302) {
const text = await response.text();
document.write(text);
} else {
// Token has been sent to WPM, move on to next step.
}
} catch {
throw new Error(
'Unable to POST embedded payment token to completePaymentUrl.',
);
}
}
IMPORTANT
Web Payment Module may return a 302
status code to indicate that a redirect is required for Strong Customer Authentication (e.g., 3DS).
The client application must handle this by overwriting the current HTML document with the response body from Web Payment Module to initiate the redirection.
Once Strong Customer Authentication has been completed, the patron will be redirected back to the payment page (i.e., redirectReturnUrl
) to confirm payment completion.
Confirm payment completion
The payment will be automatically completed after the token has been sent to WPM and Strong Customer Authentication has been completed (if required).
As payment completion occurs on a background process, poll the GetCurrentEmbeddedPayment
endpoint with an exponential backoff between requests until it no longer returns a "Pending"
status.
{
"id": "string",
"type": "Embedded",
"value": 0.1,
"status": "Completed"
}
If a "Completed"
status is returned, the payment has been completed successfully and the order should be automatically completed.
If a "Declined"
status is returned, the payment was declined by the connector. See payment errors for more info.
Confirm order completion
The order will be automatically completed after the embedded payment has been successfully completed.
NOTE
Order completion does not need to be manually requested by the client application when an embedded payment has been completed.
As order completion occurs on a background process, poll the GetOrderCompletion
endpoint with an exponential backoff between requests until it no longer returns a "Pending"
status.
{
"orderId": "string",
"orderType": "Standard",
"status": "Completed",
"transactionNumber": 0,
"updatedOrderExpiresAt": null
}
If a "Completed"
status is returned, the order has been completed successfully. The client application may now use the GetCompletedOrder endpoint to retrieve the details of the completed order.
If a "Failed"
status is returned, a post-payment failure has occurred. See order completion errors for more info.