BigCommerce: Embedded Checkout
You can skip this guide if you are using Storefront Starter for BigCommerce. This code will be already set up for you.
The BigCommerce Embedded Checkout feature lets you integrate an iframe-based version of the checkout process within your application. By incorporating this functionality, you can provide your customers with a consistent and secure checkout experience while leveraging the comprehensive range of features and payment methods supported by BigCommerce.
Unfortunately, due to changes in Chrome's default settings on 3rd party cookies, they are unavailable in incognito mode and will be unavailable in normal mode soon. But we can still use Embedded Checkout as Hosted Checkout - instead of inserting it in our checkout page as <iframe>
, we redirect the user to the Checkout hosted by BigCommerce.
Prerequisites
Before you can integrate embedded checkout into your storefront, make sure you have the following prerequisities in place:
- BigCommerce store with a dedicated Channel and Channel Site
- Store API credentials with the correct permissions
You can find a complete BigCommerce setup overview on the Embedded Checkout Tutorial page.
Middleware
First, you need to create a method that returns an embedded_checkout_url
value you will require on the Checkout page. Create a new file (and directory if needed): api/checkout/getEmbeddedCheckoutUrl.ts
We recommend to keep the custom API endpoints in the api
directory separating each extension by a separate directory. That's why in following example we will use api/checkout
.
First, you need to create an extension with a method that returns an embedded_checkout_url
value you will require on the Checkout page.
// apps/storefront-middleware-api/api/checkout/getEmbeddedCheckoutUrl.ts
import { getCartId } from "@vsf-enterprise/unified-api-bigcommerce";
import { CartIncludeEnum } from "@vsf-enterprise/bigcommerce-api";
import type { BigcommerceIntegrationContext } from "@vsf-enterprise/bigcommerce-api";
export const getEmbeddedCheckoutUrl = async (context: InternalContext) => {
let cart = null;
try {
cart = await context.api.getCart({
id: getCartId(context),
include: CartIncludeEnum.RedirectUrls,
});
} catch { }
if (cart === null) {
return { url: null }
}
if (cart.data.line_items?.physical_items?.length === 0) {
return { url: null }
}
return { url: cart.data.redirect_urls!.checkout_url }
};
- Fetch raw BigCommerce Cart Data with extra
redirect_urls
field - Return
embedded_checkout_url
value in response
Add also an index.ts
file with barrel export in api/checkout
directory.
// apps/storefront-middleware-api/api/checkout/index.ts
export * from "./getEmbeddedCheckoutUrl";
Next, you need to create an extension with a method that returns an embedded_checkout_url
value you will require on the Checkout page.
// apps/storefront-middleware-api/extensions/checkout/unified.ts
import { getEmbeddedCheckoutUrl } from "../../../api/checkout/getEmbeddedCheckoutUrl";
export const checkoutExtension = {
name: "checkout",
isNamespaced: true,
extendsApiMethods: {
getEmbeddedCheckoutUrl
}
};
- Extend API-Client methods with custom method with
extendApiMethods
property
Now, edit the index.ts
file in the same directory (integrations/bigcommerce/extensions
). Add the export for the new extension.
// apps/storefront-middleware-api/integrations/bigcommerce/extensions/index.ts
export * from "./unified";
export * from "./multistore";
export * from "./checkout";
Then, import new extension in BigCommerce integration config (integrations/bigcommerce/config.ts
).
// apps/storefront-middleware-api/integrations/bigcommerce/config.ts
import type { MiddlewareSettingsConfig } from "@vsf-enterprise/bigcommerce-api";
import type { ApiClientExtension, Integration } from "@vue-storefront/middleware";
import { multistoreExtension, unifiedApiExtension } from "./extensions"; import { multistoreExtension, unifiedApiExtension, checkoutExtension } from "./extensions";
// ...
export const bigcommerceConfig = {
location: "@vsf-enterprise/bigcommerce-api/server",
configuration: {
// ...
},
extensions: (extensions: ApiClientExtension[]) => [
...extensions,
unifiedApiExtension,
...(IS_MULTISTORE_ENABLED ? [multistoreExtension] : []),
checkoutExtension, ],
} satisfies Integration<MiddlewareSettingsConfig>;
As last step, we need to infer the type of the new extension and add it (make intersection of types) to the core UDL extension endpoints.
// apps/storefront-middleware-api/types/index.ts
import { WithoutContext } from "@vue-storefront/middleware";
import { unifiedApiExtension } from "./extensions"; import { unifiedApiExtension, checkoutExtension } from "./extensions";
export type UnifiedApiExtension = typeof unifiedApiExtension;
export type UnifiedEndpoints = WithoutContext<UnifiedApiExtension["extendApiMethods"]>;
export type CheckoutExtension = typeof checkoutExtension; export type CheckoutEndpoints = WithoutContext<CheckoutExtension["extendApiMethods"]>;
Frontend
1. Update SDK setup
Update your SDK setup to intercept an x-bigcommerce-channel-id
header in your requests. This header allows to handle a Cart in a selected channel and generate valid checkout. Also add new extension as a separate SDK extension:
// apps/storefront-unified-nextjs/sdk/config.ts
import { contentfulModule } from '@vsf-enterprise/contentful-sdk';
import { defineSdkConfig } from '@vue-storefront/next';
import type { CommerceEndpoints, UnifiedEndpoints } from 'storefront-middleware/types'; import type { CommerceEndpoints, CheckoutEndpoints, UnifiedEndpoints } from 'storefront-middleware/types';
const bcChannelId = env('NEXT_PUBLIC_BC_CHANNEL_ID');
export function getSdkConfig() {
return defineSdkConfig(({ buildModule, config, getRequestHeaders, middlewareModule }) => ({
checkout: buildModule(middlewareModule<UnifiedEndpoints>, {
apiUrl: `${middlewareUrl}/commerce/checkout`,
defaultRequestConfig: {
headers: () => {
...getRequestHeaders(),
"x-bigcommerce-channel-id": bcChannelId
},
},
}),
// ...
unified: buildModule(middlewareModule<UnifiedEndpoints>, {
apiUrl: `${config.middlewareUrl}/commerce/unified`,
cdnCacheBustingId: config.cdnCacheBustingId,
defaultRequestConfig: {
headers: getRequestHeaders(), headers: () => { ...getRequestHeaders(), "x-bigcommerce-channel-id": bcChannelId }, },
methodsRequestConfig: config.defaultMethodsRequestConfig.unifiedCommerce.middlewareModule,
}),
}));
}
- Extend an default headers
x-bigcommerce-channel-id
header - Make sure that
NEXT_PUBLIC_BC_CHANNEL_ID
is available in your.env
file
2. Rewrite Checkout Page
- Fetch Embedded Checkout URL with an extra
getEmbeddedCheckoutUrl
method.
// apps/storefront-unified-nextjs/app/[locale]/(default)/checkout/page.tsx
/* Part of this file has been omitted for readability */
import { getSdk } from '@/sdk';
export default async function Page() {
const embeddedCheckoutUrl = await getSdk().checkout.getEmbeddedCheckoutUrl();
// ...
}
- Redirect to the checkout hosted by BigCommerce.
// apps/storefront-unified-nextjs/app/[locale]/(default)/checkout/page.tsx
/* Part of this file has been omitted for readability */
import { redirect } from '@/config/navigation';
import { getSdk } from '@/sdk';
export default async function Page() {
const embeddedCheckoutUrl = await getSdk().checkout.getEmbeddedCheckoutUrl();
if (embeddedCheckoutUrl?.url && embeddedCheckoutUrl.url.length > 0) {
redirect(embeddedCheckoutUrl?.url);
} else {
return (
<>
{/*... */}
</>
);
}
}
- Add
loading.tsx
due to Checkout page is asynchronous
// apps/storefront-unified-nextjs/app/[locale]/(default)/checkout/loading.tsx
import { SfLoaderCircular } from '@storefront-ui/react';
export default function Loading() {
return (
<div className="min-h-[400px]" id="checkout">
<span className="my-40 !flex h-24 justify-center">
<SfLoaderCircular size="3xl" />
</span>
</div>
);
}
That's it. Your checkout page is now integrated with a BigCommerce Embedded Checkout.