Vue Storefront is now Alokai! Learn More
BigCommerce: Embedded Checkout

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:

  1. BigCommerce store with a dedicated Channel and Channel Site
  2. 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.

/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.

/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.

/api/bigcommerce/extensions/checkout.ts
import { getEmbeddedCheckoutUrl } from "../../../api/checkout/getEmbeddedCheckoutUrl";

export const checkoutExtension = {
  name: "checkout",
  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.

/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).

/integrations/bigcommerce/config.ts

import type { MiddlewareSettingsConfig } from "@vsf-enterprise/bigcommerce-api";
import type { ApiClientExtension, Integration } from "@vue-storefront/middleware";
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.

/integrations/bigcommerce/types.ts
import { WithoutContext } from "@vue-storefront/middleware";
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

Next.js

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:

Next.js
// sdk/sdk.config.ts
import { CreateSdkOptions, createSdk } from '@vue-storefront/next';
-import type { UnifiedEndpoints } from 'storefront-middleware/types';
+import type { CheckoutEndpoints, UnifiedEndpoints } from 'storefront-middleware/types';

export const { getSdk } = createSdk(options, ({ buildModule, middlewareModule, middlewareUrl, getRequestHeaders }) => ({
  unified: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: `${middlewareUrl}/commerce`,
    defaultRequestConfig: {
-      headers: getRequestHeaders(),
+      headers: () => {
+        ...getRequestHeaders(),
+        "x-bigcommerce-channel-id": process.env.NEXT_PUBLIC_BC_CHANNEL_ID
+      },
    },
  }),
+  checkout: buildModule(middlewareModule<UnifiedEndpoints>, {
+    apiUrl: `${middlewareUrl}/commerce`,
+    defaultRequestConfig: {
+      headers: () => {
+        ...getRequestHeaders(),
+        "x-bigcommerce-channel-id": process.env.NEXT_PUBLIC_BC_CHANNEL_ID
+      },
+    },
+  }),
  cms: buildModule(contentfulModule, {
    apiUrl: `${middlewareUrl}/cntf`,
  }),
}));
  • 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.
pages/checkout.tsx
/* Part of this file has been omitted for readability */
import { useQuery } from "@tanstack/react-query";

export function CheckoutPage() {
  const embeddedCheckoutUrl = useQuery(
    ["embedded-url"],
    sdk.checkout.getEmbeddedCheckoutUrl,
  );
  // ...
}
  • Redirect to the checkout hosted by BigCommerce.
pages/checkout.tsx
/* Part of this file has been omitted for readability */
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "next/router";
import { useSdk } from "~/sdk";

function CheckoutPageContent() {
  const router = useRouter();
  const sdk = useSdk();
  const embeddedCheckoutUrl = useQuery(
    ["embedded-url"],
    sdk.checkout.getEmbeddedCheckoutUrl,
  );

  if (embeddedCheckoutUrl.isError) {
    router.push(appRoutes.index.compile());
  }

  if (
    embeddedCheckoutUrl.data?.url &&
    embeddedCheckoutUrl.data.url.length > 0
  ) {
    window.location.href = embeddedCheckoutUrl.data.url;
  } else if (!embeddedCheckoutUrl.isLoading) {
    router.push(appRoutes.index.compile());
  }

  return <div className="min-h-[400px]" id="checkout" />;
}
  • Render CheckoutPageContent component in the CheckoutLayout.
// pages/checkout.tsx

/* Part of this file has been omitted for readability */
import { CheckoutLayout } from "~/layouts";
import { useTranslation } from "next-i18next";
import { appRoutes } from "~/config";

function CheckoutPageContent() {
  // ...
}

export function CheckoutPage() {
  const { t } = useTranslation("checkout");

  return (
    <CheckoutLayout
      backHref={appRoutes.cart.compile()}
      backLabel={t("back")}
      heading={t("checkout")}
    >
      <CheckoutPageContent />
    </CheckoutLayout>
  );
}

Nuxt

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.

-import type { UnifiedEndpoints } from '../storefront-middleware/types';
+import type { CheckoutEndpoints, UnifiedEndpoints } from '../storefront-middleware/types';

export default defineSdkConfig(({ buildModule, middlewareModule, middlewareUrl, getRequestHeaders }) => ({
  unified: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: `${middlewareUrl}/commerce`,
    defaultRequestConfig: {
-      headers: getRequestHeaders(),
+      headers: () => {
+        ...getRequestHeaders(),
+        "x-bigcommerce-channel-id": process.env.NUXT_PUBLIC_BC_CHANNEL_ID
+      },
    },
  }),
+  checkout: buildModule(middlewareModule<CheckoutEndpoints>, {
+    apiUrl: `${middlewareUrl}/commerce`,
+    defaultRequestConfig: {
+      headers: () => {
+        ...getRequestHeaders(),
+        "x-bigcommerce-channel-id": process.env.NUXT_PUBLIC_BC_CHANNEL_ID
+      },
+    },
+  }),
}));
  • Add x-bigcommerce-channel-id header in SDK requests
  • Make sure that NUXT_PUBLIC_BC_CHANNEL_ID is available in your .env file

2. Rewrite Checkout Page

// pages/checkout.vue
<template>
  <NuxtLayout
    name="checkout"
    :back-href="paths.cart"
    :back-label-desktop="$t('backToCart')"
    :back-label-mobile="$t('back')"
    :heading="$t('checkout')"
  >
    <div class="relative min-h-[400px]">
      <span class="!flex justify-center my-40 h-24">
        <SfLoaderCircular size="3xl" />
      </span>
    </div>
  </NuxtLayout>
</template>

<script setup lang="ts">
import { SfLoaderCircular } from '@storefront-ui/vue';
import { useSdk } from '~/sdk';

definePageMeta({
  layout: false,
});

const router = useRouter();

onMounted(() => {
  useSdk().checkout.getEmbeddedCheckoutUrl()
    .then(({ url }) => {
      if (url && url.length > 0) {
        window.location.href = url;
      } else {
        router.push(paths.home);
      }
    })
    .catch(e => {
      router.push(paths.home);
    });
});
</script>

That's it. Your checkout page is now integrated with a BigCommerce Embedded Checkout.

Embedded Checkout Page