The Vue Storefront Essentials Course is now available! Learn More
BigCommerce: Embedded Checkout

BigCommerce: Embedded Checkout

Check out a complete implementation in the unified-storefront-demo#bigcommerce branch.

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 add a method to the UnifedExtension that returns an embedded_checkout_url value you will require on the Checkout page.

// middleware.config.ts
import { Context, methods, getCartId, normalizers, createUnifiedExtension } from "@vsf-enterprise/unified-api-bigcommerce";
import { CartIncludeEnum } from "@vsf-enterprise/bigcommerce-api";

const unifiedApiExtension = createUnifiedExtension<Context>()({
  normalizers,
  apiMethods: {
    ...methods<typeof normalizers>(),
    getEmbeddedCheckoutUrl: async (context) => {
      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 }
    },
  },
});
  • Extend API-Client methods with custom method with extendApiMethods property
  • Fetch raw BigCommerce Cart Data with extra redirect_urls field
  • Return embedded_checkout_url value in response

Now, a custom getEmbeddedCheckoutUrl method should be now available in the SDK.

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.

// sdk/index.ts
export const sdk = initSDK({
  unified: buildModule(unifiedModule<UnifiedApiExtension>, {
    apiUrl: `${API_BASE_URL}/commerce`,
    requestOptions: {
      headers: {         "x-bigcommerce-channel-id": process.env.NEXT_PUBLIC_BC_CHANNEL_ID,       },     },
  }),
});
  • Extend an Unified SDK module with requestOptions.headers
  • Add x-bigcommerce-channel-id header in SDK requests
  • 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.unified.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 { sdk } from "~/sdk";

function CheckoutPageContent() {
  const router = useRouter();
  const embeddedCheckoutUrl = useQuery(
    ["embedded-url"],
    sdk.unified.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.

// sdk/index.ts
export function useSdk() {
  const config = useRuntimeConfig();
  const { apiBaseUrl, apiProtocol, apiSubpath } = config.public;
  const middlewareUrl = composeMiddlewareUrl(apiBaseUrl, apiProtocol, apiSubpath);
  const sdkConfig = {
    // ...
    unified: buildModule(unifiedModule<UnifiedApiExtension>, {
      apiUrl: middlewareUrl + '/commerce',
      requestOptions: {
        headers: () => ({
          ...useRequestHeaders(['cookie']),
          'x-bigcommerce-channel-id': process.env.NUXT_PUBLIC_BC_CHANNEL_ID,
        }),
      },
    }),
  };

  return initSDK<typeof sdkConfig>(sdkConfig);
};
  • Add x-bigcommerce-channel-id header in SDK requests
  • Make sure that NEXT_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().unified.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