` elements are present.
You can display custom and vendor buttons only for payment integrations rendered via the web components [Payment Integration Type](/checkout/connectors-and-applications.md#payment-integration-types).
When rendering the Payment UI component as an [intermediate step](/checkout/browser-sdk.md#payment-as-intermediate-step) of the checkout process, it is mandatory to display a custom payment button.
```javascript title="Display payment and vendor buttons and react to Message to hide payment button"
```
### Accept terms and conditions before payment
When using the `paymentFlow` method, you can request the customer to accept terms and conditions before proceeding with the payment. To do so, you must provide an HTML element with a `data-ctc-selector="termsAndConditionsPending"` attribute in it until the customer accepts the required terms and conditions.
An [External Terms And Conditions Pending](/checkout/messages.md#external-terms-and-conditions-pending) Message generates if the customer tries to proceed with the payment while the element is present.
```javascript title="HTML element for pending acceptance of terms and conditions"
{
areTermsAndConditionsPending ? (
) : null;
}
```
### React to Messages
When using the `paymentFlow` method, it is important to react to the [Messages](/checkout/browser-sdk.md#message-subscription-and-logging) generated by Checkout to provide feedback to customers and handle different scenarios that may occur during the payment process.
For example, if the Cart contains Discount Codes that don't apply to it, Checkout removes the codes to avoid errors when converting the Cart to an Order. When this happens, Checkout generates the [NotApplicableDiscountCodeRemoved](/checkout/messages.md#not-applicable-discount-code-removed) Message, which you can react to, for example to provide feedback to your customer or to update the cart's information on your checkout flow.
Following is an example with some Messages that you should consider reacting to.
It is your responsibility to react to the `payment_failed` and `payment_cancelled` Messages and to give feedback to the customer about the failure. To allow the customer to retry the payment, while also ensuring the Cart has the latest version and correct total amount, call the `paymentFlow` method with the same `sessionId` again.
```javascript title="React to Messages using the 'paymentFlow' method"
import { paymentFlow } from '@commercetools/checkout-browser-sdk';
paymentFlow({
projectKey: '{projectKey}',
region: '{region}',
sessionId: '{sessionId}',
locale: `{locale}`,
logInfo: true,
logWarn: true,
logError: true,
onError: (error) => {
switch (error.code) {
case 'payment_method_loading_error':
// Store the number of failed payment integrations on the local state.
// If the number of failed payment integrations is equal to the total number of payment integrations, display a generic error message.
break;
case 'payment_failed':
// Show a payment failed message to the customer.
// Call paymentFlow method again to restart the payment flow.
break;
}
},
onInfo: (message) => {
switch (message.code) {
case 'checkout_completed':
const {
order: { id },
} = message.payload as {
order: { id: string };
};
// Redirect to confirmation page with Order ID.
break;
case 'payment_method_selection_confirmation_failed':
// Show message to customer and allow user to try again.
break;
case 'payment_method_selection_confirmation': {
const {
method: { hasVendorButton, type },
} = message.payload as {
method: { type: string; hasVendorButton: boolean };
};
// Hide custom payment button if hasVendorButton is 'true'.
break;
}
case 'payment_methods_received': {
const { paymentMethods } = message.payload as { paymentMethods: string[] };
// Store total number of payment integrations on local state.
break;
}
case 'payment_method_loaded':
// Increment loaded payment integrations.
break;
case 'payment_started':
// Show blocking overlay with spinner to avoid user interaction with the page.
// Initialize local state errors to 'false'.
break;
case 'payment_completed':
// Hide blocking overlay.
break;
case 'payment_method_selected':
const {
method: { hasVendorButton, type },
} = message.payload as {
method: { type: string; hasVendorButton: boolean };
};
// Store selected payment integration on local state and remove local payment error state.
break;
case 'payment_cancelled':
// Hide blocking overlay.
break;
case 'external_terms_and_conditions_pending':
// Show message to the customer to accept terms and conditions.
break;
}
},
});
```
## Express Payments
[Express Payments](/checkout/connectors-and-applications.md#express-payments) let you render payment buttons, such as Apple Pay and Google Pay, anywhere on your website to complete an express checkout that bypasses the multi-step checkout process.
Express Payments use a two-step integration pattern:
1. **Initialize once**: call `expressPayment.init()` to set up the configuration and Message handlers.
2. **Mount multiple times**: call `expressPayment.mount()` for each location where you want to display express payment buttons.
### Express Payment configuration properties
The following table shows the configuration properties to provide with the `expressPayment.init()` method:
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `projectKey` | string | Yes | Identifier of your Checkout entity and the [`project_key`](/api/getting-started/create-api-client.md) of your Project. |
| `sessionId` | string | Yes | Identifier of the [Checkout Session](/checkout/installing-checkout.md#create-checkout-sessions). |
| `region` | string | Yes | [Region](/checkout/hosts-and-authorization.md) where your Checkout application is hosted. For example, `europe-west1.gcp`. |
| `countryCode` | string | Yes | The country code of your store (ISO 3166-1 alpha-2). For example, `DE`. |
| `locale` | string | No (default: `en`) | Your customer's [locale](/checkout/installing-checkout.md#locales). If the provided locale is not found, then English will be used. |
| `onInfo` | function | No | For more information, see [onInfo, onWarn, and onError](/checkout/browser-sdk.md#oninfo-onwarn-and-onerror). |
| `onWarn` | function | No | For more information, see [onInfo, onWarn, and onError](/checkout/browser-sdk.md#oninfo-onwarn-and-onerror). |
| `onError` | function | No | For more information, see [onInfo, onWarn, and onError](/checkout/browser-sdk.md#oninfo-onwarn-and-onerror). |
| `logInfo` | boolean | No (default: `false`) | For more information, see [logInfo, logWarn, and logError](/checkout/browser-sdk.md#loginfo-logwarn-and-logerror). |
| `logWarn` | boolean | No (default: `false`) | For more information, see [logInfo, logWarn, and logError](/checkout/browser-sdk.md#loginfo-logwarn-and-logerror). |
| `logError` | boolean | No (default: `false`) | For more information, see [logInfo, logWarn, and logError](/checkout/browser-sdk.md#loginfo-logwarn-and-logerror). |
### Initialize the Express Payment
Before mounting Express Payments buttons, you must initialize the Express Payment once with your configuration. This is typically done when the page loads or when the session is created.
```javascript title="Initialize Express Payment"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
expressPayment.init({
projectKey: '{projectKey}',
region: '{region}',
sessionId: '{sessionId}',
countryCode: '{countryCode}',
locale: 'en', // Optional: defaults to 'en'
logInfo: true,
logWarn: true,
logError: true,
onInfo: (message) => {
switch (message.code) {
case 'express_payment_started':
// Show blocking overlay with spinner.
break;
case 'express_payment_cancelled':
// Hide blocking overlay.
break;
case 'express_payment_completed':
// Redirect to confirmation page with Order ID.
const { order } = message.payload as { order: { id: string } };
window.location.href = '/thank-you?orderId=' + order.id;
break;
}
},
onError: (error) => {
switch (error.code) {
case 'express_payment_failed':
// Show error message to customer.
break;
}
},
});
```
The `sessionId` used for Express Payments does not need to have a Cart associated with it initially. You can create or update the Cart in the `onPayButtonClick` callback when the customer clicks the Express Payments button.
### Use Express Payments with a browser script
When integrating as a browser script, use the `ctc` function to initialize and mount the Express Payments:
```HTML title="Initialize and mount Express Payment with browser script"
```
When using the browser script, use `expressPaymentInit` and `expressPaymentMount` as function names instead of `expressPayment.init` and `expressPayment.mount`.
### Mount Express Payments buttons
After initialization, you can mount Express Payments buttons to one or more containers on your page. You can choose between two approaches:
1. **Mount all buttons at once**: use `expressPayment.mount()` to render all available Express Payments buttons in a single container.
2. **Mount individual buttons**: use `expressPayment.getAvailableMethods()` to discover available methods, then `expressPayment.mountMethod()` to mount each button individually for custom layouts.
#### Mount all buttons in a single container
The `mount()` method renders all available express payment buttons in an element with the `data-ctc-express` attribute:
```html title="HTML container for express buttons"
```
```javascript title="Mount all Express Payment buttons"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
// No expressId needed - SDK finds the element with data-ctc-express attribute
expressPayment.mount({
initialAmount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 2999,
fractionDigits: 2,
},
});
```
#### Mount individual buttons with custom layout
For more control over the layout and placement of express buttons, you can mount them individually. Use the `expressId` option to target specific containers with `data-ctc-express="expressId"`:
```html title="HTML containers for individual express buttons"
```
```javascript title="Mount individual Express Payment buttons"
import { expressPayment, type ExpressPaymentMethod } from '@commercetools/checkout-browser-sdk';
// Get available express payment methods
const methods = await expressPayment.getAvailableMethods();
// Returns: [{ type: 'googlepay', paymentIntegrationId: '...', connectorId: '...' }, ...]
// Mount each method to its own container
methods.forEach((method) => {
expressPayment.mountMethod({
expressId: method.type, // Targets
method,
initialAmount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 2999,
fractionDigits: 2,
},
onPayButtonClick: async () => {
await createCartWithProduct(productId);
},
});
});
```
This approach lets you:
- Place buttons in different locations on the page
- Control the order in which buttons appear
- Wrap buttons in custom decorator components
- Conditionally show or hide specific payment methods
The `getAvailableMethods()` method returns the list of Express Payments methods configured for your project. The available methods depend on your payment connector configuration and may vary at runtime.
#### Use the onPayButtonClick hook
The `onPayButtonClick` callback is optional when Express Checkout is initialized with an existing Cart already attached to the session.
However, it becomes mandatory if a Cart was not passed during session initialization. For example, when Express Checkout is rendered on a Product Detail Page (PDP) where a Cart does not yet exist. In this case, the callback should be used when creating the Cart at the time the customer clicks the Express Payments button, and then update the original session with the newly created `cartId` before proceeding with the payment flow.
It is called when the customer clicks an Express Payments button. Use it to perform async operations before the payment starts, such as creating or updating a Cart. You can also use it for any other async operations you need.
When the Express Payments flow starts, the Checkout Session **must** have a Cart associated with it. If no Cart is present at this point, the Express Payments flow will fail.
The following examples show some possible implementations:
```javascript title="Example: Create a Cart when the express button is clicked"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
expressPayment.mount({
initialAmount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 2999,
fractionDigits: 2,
},
onPayButtonClick: async () => {
// Create a Cart with the product and update the session.
const cart = await createCart();
await addProductToCart(cart.id, productId);
await updateSessionWithCart(sessionId, cart.id);
},
});
```
```javascript title="Example: Perform other async operations"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
expressPayment.mount({
initialAmount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 2999,
fractionDigits: 2,
},
onPayButtonClick: async () => {
// Perform any async operations before the express payment starts.
await trackAnalyticsEvent('express_payment_started');
await validateInventory(productId);
},
});
```
### Mount options
The following table shows the options to provide with the `expressPayment.mount()` and `expressPayment.mountMethod()` methods:
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `expressId` | String | No | Identifier to find a specific container with `data-ctc-express="expressId"`. If not provided, the SDK looks for an element with just the `data-ctc-express` attribute. |
| `initialAmount` | Object | Yes | The initial amount to display on the Express Payments buttons. Contains `currencyCode`, `centAmount`, `fractionDigits`, and optionally `type` (must be `'centPrecision'` if provided). |
| `method` | Object | Only for `mountMethod()` | The payment method to mount. Obtained from `getAvailableMethods()`. Contains `type`, `paymentIntegrationId`, and `connectorId`. |
| `onPayButtonClick` | Function | No | Optional hook called when the customer clicks an Express Payments button. Use this to perform async operations before the checkout starts, such as creating or updating a Cart. Must return a Promise. |
| `onShippingAddressSelected` | Function | No | Callback function called when the customer selects a shipping address in the payment sheet. Receives a partial address (country, postalCode, city, state). If not provided, the Cart is automatically updated with the partial address. |
| `getShippingMethods` | Function | No | Callback function to provide custom shipping methods. Must return a Promise that resolves to an array of shipping options. If not provided, shipping methods are fetched automatically. |
| `onShippingMethodSelected` | Function | No | Callback function called when the customer selects a shipping method. If not provided, the Cart is automatically updated with the selected shipping method. |
| `onPaymentSubmit` | Function | No | Callback function called before the payment is submitted with the complete shipping and billing addresses and customer email. If not provided, the Cart is automatically updated with the addresses and email. |
### Handle shipping with Express Payment
Express Payment supports two approaches for handling shipping:
#### Automatic shipping handling
By default, Express Payments automatically handles shipping methods based on your Project configuration. The shipping methods matching the customer's address are displayed in the payment sheet.
#### Custom shipping methods
If you need more control over shipping methods, you can provide custom shipping methods using the `getShippingMethods` and `onShippingMethodSelected` callbacks.
```javascript title="Custom shipping methods"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
expressPayment.mount({
initialAmount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 2999,
fractionDigits: 2,
},
onPayButtonClick: async () => {
// Create a Cart when the customer clicks the express button.
await createCartWithProduct(productId);
},
getShippingMethods: async () => {
// Return custom shipping methods.
return [
{
id: 'standard',
name: 'Standard Shipping',
description: '5-7 business days',
amount: {
centAmount: 499,
currencyCode: 'EUR',
},
},
{
id: 'express',
name: 'Express Shipping',
description: '1-2 business days',
isSelected: true,
amount: {
centAmount: 999,
currencyCode: 'EUR',
},
},
];
},
onShippingMethodSelected: async ({ shippingMethod }) => {
// Update the Cart with the selected shipping method.
await updateCartShippingMethod(shippingMethod.id);
},
});
```
When using custom shipping methods, you must provide both `getShippingMethods` and `onShippingMethodSelected` callbacks. The `getShippingMethods` callback is called when the payment sheet is displayed, and `onShippingMethodSelected` is called when the customer selects a shipping method.
### Handle addresses with Express Payments
Express Payments provides callbacks to handle shipping and billing addresses:
#### Partial address selection
The `onShippingAddressSelected` callback is called when the customer selects a shipping address in the payment sheet. At this stage, only a partial address is available (country, postalCode, city, and state) for privacy reasons.
```javascript title="Handle partial address selection"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
expressPayment.mount({
initialAmount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 2999,
fractionDigits: 2,
},
onShippingAddressSelected: async ({ address }) => {
// Use the partial address to calculate shipping costs or validate delivery.
console.log('Partial address:', address.country, address.postalCode);
},
});
```
#### Complete address handling
The `onPaymentSubmit` callback is called before the payment is submitted and provides the complete shipping and billing addresses along with the customer email. Use this callback to perform final Cart updates or validations.
```javascript title="Handle complete addresses and customer email"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
expressPayment.mount({
initialAmount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 2999,
fractionDigits: 2,
},
onPaymentSubmit: async ({ shippingAddress, billingAddress, customerEmail }) => {
// Update the Cart with the complete addresses and customer email.
await updateCartAddresses(shippingAddress, billingAddress);
await updateCartEmail(customerEmail);
},
});
```
If you do not provide the `onPaymentSubmit` callback, the Cart is automatically updated with the shipping and billing addresses and customer email received from the payment provider.
### React to Express Payments Messages
When using Express Payments, it is important to react to the [Messages](/checkout/browser-sdk.md#message-subscription-and-logging) generated by Checkout to provide feedback to customers and handle different scenarios.
```javascript title="React to Express Payment Messages"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
expressPayment.init({
projectKey: '{projectKey}',
region: '{region}',
sessionId: '{sessionId}',
countryCode: '{countryCode}',
onInfo: (message) => {
switch (message.code) {
case 'express_payment_started':
// Customer clicked an express payment button.
// Show blocking overlay with spinner.
break;
case 'express_payment_cancelled':
// Customer cancelled the express payment.
// Hide blocking overlay.
break;
case 'express_payment_interrupted':
// Express payment was interrupted by the seller's callback.
// Hide blocking overlay.
break;
case 'express_payment_completed':
// Express payment completed successfully.
const { order } = message.payload as { order: { id: string } };
window.location.href = '/thank-you?orderId=' + order.id;
break;
}
},
onError: (error) => {
switch (error.code) {
case 'express_payment_failed':
// Express payment failed.
// Show error message to customer.
break;
case 'no_express_payment_integrations':
// No express payment integrations are configured.
// Hide express buttons container.
break;
case 'express_container_not_found':
// The container element was not found.
break;
case 'express_multiple_containers_found':
// Multiple elements with data-ctc-express attribute found.
// Use expressId to specify which container to use.
break;
case 'express_payment_integration_not_available':
// The requested payment integration is not available.
// This can happen when using mountMethod() with an unavailable method.
break;
}
},
});
```
It is your responsibility to react to the `express_payment_failed` and `express_payment_cancelled` Messages and to give feedback to the customer. Unlike the `checkoutFlow` and `paymentFlow` methods, Express Payment does not display error pages automatically.
### Complete Express Payments example
The following example shows a complete integration of Express Payments on a product page using `mount()` to render all buttons in a single container:
```html title="HTML structure"
```
```javascript title="Complete Express Payment integration"
import { expressPayment } from '@commercetools/checkout-browser-sdk';
// Initialize Express Payment once when the page loads.
const initializeExpressPayment = (sessionId: string) => {
expressPayment.init({
projectKey: '{projectKey}',
region: '{region}',
sessionId: sessionId,
countryCode: 'DE',
locale: 'de-DE', // Optional: defaults to 'en'
logInfo: true,
logWarn: true,
logError: true,
onInfo: (message) => {
switch (message.code) {
case 'express_payment_started':
showBlockingOverlay();
break;
case 'express_payment_cancelled':
hideBlockingOverlay();
break;
case 'express_payment_completed':
hideBlockingOverlay();
const { order } = message.payload as { order: { id: string } };
window.location.href = '/thank-you?orderId=' + order.id;
break;
}
},
onError: (error) => {
hideBlockingOverlay();
if (error.code === 'express_payment_failed') {
showErrorMessage('Express payment failed. Please try again.');
}
},
});
};
// Mount express buttons (no expressId needed for single container).
const mountExpressButtons = (product: { id: string; price: number; currency: string }) => {
expressPayment.mount({
initialAmount: {
type: 'centPrecision',
currencyCode: product.currency,
centAmount: product.price,
fractionDigits: 2,
},
onPayButtonClick: async () => {
// Create a Cart with the product.
await createCartWithProduct(product.id);
},
getShippingMethods: async () => {
return getAvailableShippingMethods(product.currency);
},
onShippingMethodSelected: async ({ shippingMethod }) => {
await setCartShippingMethod(shippingMethod.id);
},
});
};
```
### Complete Express Payments example with individual buttons
The following example shows how to mount buttons individually for custom layouts:
```html title="HTML structure for individual buttons"
```
```javascript title="Express Payments with individual buttons"
import { expressPayment, type ExpressPaymentMethod } from '@commercetools/checkout-browser-sdk';
// Initialize Express Payments once when the page loads.
const initializeExpressPayment = (sessionId: string) => {
expressPayment.init({
projectKey: '{projectKey}',
region: '{region}',
sessionId: sessionId,
countryCode: 'DE',
locale: 'de-DE', // Optional: defaults to 'en'
logInfo: true,
logWarn: true,
logError: true,
onInfo: (message) => {
if (message.code === 'express_payment_completed') {
const { order } = message.payload as { order: { id: string } };
window.location.href = '/thank-you?orderId=' + order.id;
}
},
});
};
// Mount express buttons individually for a product.
const mountIndividualExpressButtons = async (product: { id: string; price: number; currency: string }) => {
// Get available Express Payments methods
const methods = await expressPayment.getAvailableMethods();
// Common mount options
const commonOptions = {
initialAmount: {
type: 'centPrecision' as const,
currencyCode: product.currency,
centAmount: product.price,
fractionDigits: 2,
},
onPayButtonClick: async () => {
await createCartWithProduct(product.id);
},
};
// Mount each method to its own container
methods.forEach((method) => {
expressPayment.mountMethod({
expressId: method.type, // Targets
, etc.
method,
...commonOptions,
});
});
};
```
You can also dynamically create containers if the available methods are not known at build time:
```javascript title="Dynamically creating containers"
// Mount each method to its own dynamically created container
methods.forEach((method) => {
const expressId = method.type;
// Create container element if it doesn't exist
if (!document.querySelector(`[data-ctc-express="${expressId}"]`)) {
const container = document.createElement('div');
container.setAttribute('data-ctc-express', expressId);
container.className = 'express-button-wrapper';
document.querySelector('.express-buttons-row')?.appendChild(container);
}
expressPayment.mountMethod({
expressId,
method,
...commonOptions,
});
});
```
## close method
Use the `close` method to close the Checkout or the Payment UI component when it is not needed. Calling the `close` method is recommended over unmounting the component, as it ensures that the necessary cleanup is performed.
An example of use is a single-page application (SPA) implementing a [multi-step checkout process](/checkout/browser-sdk.md#paymentflow-method-in-multi-step-checkouts) with the `paymentFlow` [method](/checkout/browser-sdk.md#paymentflow-method), where the customer decides to edit the information entered in a previous step. In such cases, you can use the `close` method to close the payment UI component.
Once the customer moves the payment step again, you can call the `paymentFlow` method with the same `sessionId` to render the Payment UI component again.
Another example of a use case is to implement a custom button to leave the checkout when using the `checkoutFlow` [method](/checkout/browser-sdk.md#checkoutflow-method).
```javascript title="Call the 'close' method"
import { close } from '@commercetools/checkout-browser-sdk';
close();
```
## Return URL
Some payment integrations may require a redirection from your website to one of the payment service providers (PSPs) to get information about the customer. In such cases, when you install a [Connector](/checkout/connectors-and-applications.md#payment-connectors), you must set a return URL to take the customer back to your website after the information has been retrieved. We recommend using the same URL as the one where the checkout process started; however, this is not mandatory.
You can set only one return URL per Connector. If you want to use the same Connector with multiple domains, you must create different Applications in the Merchant Center and override the return URL.
When information has been retrieved, the `paymentReference` parameter is added to the return URL with the `id` of the [Payment](/search.md?urn=ctp:api:type:Payment) as its value, and the customer returns to your website. Now, you must call the `checkoutFlow` or `paymentFlow` method again by providing the same configuration parameters as before along with the `paymentReference`.
```javascript title="Call the 'checkoutFlow' method with the 'paymentReference' parameter"
import { checkoutFlow } from '@commercetools/checkout-browser-sdk';
checkoutFlow({
region: '{region}',
projectKey: '{projectKey}',
sessionId: '{sessionId}',
locale: '{locale}',
logInfo: true,
logWarn: true,
logError: true,
paymentReference: '{paymentReference}',
});
```
When calling the `checkoutFlow` or `paymentFlow` method again, the UI component appears and displays a loading status while the payment is verified. If the payment is successful, the checkout process is complete. Otherwise, the customer is prompted to either quit the checkout process or try again by editing the entered information.
## Checkout completion
Once the customer completes the checkout process successfully, the SDK closes the Checkout or the payment UI component and the [Checkout Completed](/checkout/messages.md#checkout-completed) Message generates. After this happens, you can determine how the checkout process should continue by [reacting](/checkout/browser-sdk.md#message-subscription-and-logging) to the Message, both when using the `checkoutFlow` and the `paymentFlow` method.
For example, you can react to the Message to display feedback for the customer on your website, such as a message or a dialog. Or, you can use the Message to redirect the customer to a different page, such as a Thank You page.
```javascript title="React to Checkout Completed Message and redirect to Thank you page"
import { checkoutFlow } from '@commercetools/checkout-browser-sdk';
checkoutFlow({
projectKey: '{projectKey}',
region: '{region}',
sessionId: '{sessionId}',
onInfo: (message) => {
if (message.code === 'checkout_completed') {
const {
order: { id },
} = message.payload as {
order: { id: string };
};
window.location.href = '/thank-you?orderId=' + id;
}
},
});
```
### Retry order verification
Sometimes a checkout cannot complete verification on time and times out before the Order is created. When this happens, Checkout generates the [Order Verification Timeout](/checkout/messages.md#order-verification-timeout) Message. To retry the verification, use the `retryOrderVerification` method.
If the retry attempt fails, then Checkout generates the [Order Verification Retry Error](/checkout/messages.md#order-verification-retry-error) Message with details about why the retry failed.
```javascript title="Call the 'retryOrderVerification method' to retry order verification"
import { retryOrderVerification } from '@commercetools/checkout-browser-sdk';
retryOrderVerification();
```
## Message subscription and logging
The [Messages from Checkout](/checkout/messages.md) can be of three severity levels: `info`, `warn`, or `error`.
You can handle Messages by using:
- The `onInfo`, `onWarn`, and `onError` [handlers](/checkout/browser-sdk.md#oninfo-onwarn-and-onerror) to subscribe to Messages.
- The `logInfo`, `logWarn`, and `logError` [methods](/checkout/browser-sdk.md#loginfo-logwarn-and-logerror) to log Messages.
### onInfo, onWarn, and onError
You can subscribe to the `info`, `warn`, or `error` Messages by passing the optional `onInfo`, `onWarn`, or `onError` message handlers with the `checkoutFlow` or `paymentFlow` methods. The handlers will receive the Message as a parameter.
In the following examples, we subscribe to all `info` Messages and log a `Received` note followed by the Message code on the browser's developer console.
```javascript title="Use 'onInfo' with the npm module to subscribe to info Messages"
import { checkoutFlow } from '@commercetools/checkout-browser-sdk';
checkoutFlow({
projectKey: '{projectKey}',
region: '{region}',
sessionId: '{sessionId}',
onInfo: (message) => {
console.error('Received: ' + message.code);
},
});
```
```HTML title="Use 'onInfo' with the browser script to subscribe to info Messages"
```
### logInfo, logWarn, and logError
You can use the `logInfo`, `logWarn`, and `logError` methods to log Messages in the console for debugging purposes. We recommend not activating them in production since, usually, these logs are not useful for end users.
In the following examples, we log all the `info` and `error` Messages, but not the `warn` ones.
```javascript title="Use 'logInfo' and 'logError' with the npm module to log info and error Messages"
import { checkoutFlow } from '@commercetools/checkout-browser-sdk';
checkoutFlow({
projectKey: '{projectKey}',
region: '{region}',
sessionId: '{sessionId}',
logInfo: true,
logWarn: false,
logError: true,
});
```
```HTML title="Use 'logInfo' and 'logError' with the browser script to log info and error Messages"
```
## Related pages
- [Area overview page with navigation](/checkout.md)
- [Previous page: Overview](/checkout/installing-checkout.md)
- [Next page: Events](/checkout/checkout-events.md)
- [Search documentation and API specs](/search.md)