Displaying the Bundles widget on the Affinity overview page

Drive build-a-box customers to the portal with unique opportunities on the Affinity overview page

Businesses specializing in customizable bundles (ie. build-a-box), want to enable customers to seamlessly update their bundle content. This functionality often represents the primary reason customers visit the customer portal.

This guide outlines the steps to integrate and display the Bundles widget in Affinity's header slot, ensuring a seamless and user-friendly interface for customers managing their subscriptions.

📘

Platform:

  • Shopify Checkout Intergration
  • Recharge Checkout on Shopify

How it works

Customers can update their bundled content within the subscription details view by default.

You may want to consider transferring this information to Affinity's entry page to improve the customer experience and engagement.

The widget for managing bundle subscriptions is hosted on a CDN. Embedding it is straightforward, requiring only the inclusion of the CDN link and the configuration of the global object. Affinity offers specific extension points (slots) for integrating this interface.

📘

Note:

You must be on the Recharge Pro plan, or a Custom plan, to make advanced customizations to the Affinity customer portal.


Integrating the bundles widget into Affinity's header slot

Step 1: Slot preparation

  • Slot selection: Use the overview.header to display the widget.
  • Target element: Create an HTML element with the unique ID affinity-bundle-widget within the slot content. Use this element to mount the app.

Step 2: App Initializion

  • DOM tracking: Listen for the Recharge::slot::mounted to confirm the slot content was added to the DOM.

Step 3: Fetch Bundle Subscriptions

  • SDK usage: Use the SDK to fetch active customer subscriptions. Include bundle_product in your query to filter.
  • Subscription identification: Locate the first subscription that meets the criteria for a customizable bundle.

Step 4: Widget mounting

  • Context setting: Assign the subscriptionID to window.BoxConfig.subscriptionConfig to set the necessary context for the Bundles widget.
  • Widget loading: Trigger the Recharge::bundling-widget::reload event to initiate the widget.

Step 5: Styling

  • Style consistency: Leverage Affinity's global CSS variables to ensure your styles aligns with your app's appearance.
  • CSS inclusion: Incorporate the Bundles' widget CSS from the Recharge CDN.

Example

<link
  rel="stylesheet"
  href="https://static.rechargecdn.com/assets/bundling-widget/src.css"
  referrerpolicy="origin">
<script src="https://static.rechargecdn.com/assets/bundling-widget/src.js"></script>

<style>
  .aff-card {
    background: var(--recharge-cards-background);
    border: 1px solid var(--recharge-cards-border-color);
    border-radius: var(--recharge-corners-radius);
    padding: 20px;
    line-height: 1.4;
  }
  .aff-button {
    background-color: var(--recharge-button-brand);
    border: 2px solid var(--recharge-button-brand);
    border-radius: var(--recharge-button-border-radius);
    color: var(--recharge-button-color) !important;
    font-size: var(--recharge-typography-size-5);
    font-weight: 600;
    line-height: 150%;
    padding: 10px 16px;
    text-align: center;
    cursor: pointer;
    display: inline-block;
  }
  .aff-button[disabled=true] {
    opacity: 0.6;
  }
  .aff-h3 {
    font-size: var(--recharge-typography-size-3);
    font-weight: bold;
    line-height: 123%;
  }
  .t-mb2 {
    margin-bottom: 8px;
  }
  .t-mb3 {
    margin-bottom: 16px;
  }
</style>

{% comment %} Slots {% endcomment %}
<script type="text/html" data-recharge-slot="overview.header">
<div id="affinity-bundle-section" class="aff-card t-mb3" style="display:none;">
  <span class="aff-h3 t-mb2">Your box contents</span>
  <div id="affinity-bundle-widget" class="affinity-bundle-widget"></div>
  </div>  
</script>

<script>
const rechargeAPI = {
  session: null,

  async authenticate() {
    if (!this.session) {
      this.session = await recharge.auth.loginCustomerPortal().catch(error => {
        console.error("Authentication failed:", error);
        throw error;
      });
    }
    return this.session;
  },

  async fetchSubscriptions() {
    const response = await recharge.subscription.listSubscriptions(this.session, {
      limit: 10,
      sort_by: 'id-asc',
      status: 'Active',
      include: 'bundle_product'
    }).catch(error => {
      console.error("Fetching subscriptions failed:", error);
      throw error;
    });
    return response.subscriptions;
  }
};

const bundlesWidgetManager = {
  wrapperContainerId: 'affinity-bundle-section',
  targetContainerId: 'affinity-bundle-widget',

  async getBundleSubscription() {
    const subscriptions = await rechargeAPI.fetchSubscriptions();
    return subscriptions.find(sub => sub.include.bundle_product);
  },

  mountWidget(subscriptionId) {
    window.BoxConfig = {
      containerId: this.targetContainerId,
      subscriptionConfig: { subscriptionId }
    };
    stateManager.refreshBundlesWidget();
    this.handleBundleUpdate();
    this.showWrapperSection();
  },

  showWrapperSection() {
    document.getElementById(this.wrapperContainerId).style.display = 'block';
  },

  handleBundleUpdate() {
    document.addEventListener(stateManager.widgetEventsToHandle.subscriptionUpdated, () => {
      stateManager.refreshBundlesWidget();
    });
  },

  async init() {
    try {
      await rechargeAPI.authenticate();
      const bundleSubscription = await this.getBundleSubscription();
      if (bundleSubscription) this.mountWidget(bundleSubscription.id);
    } catch (error) {
      console.error("Initialization failed:", error);
    }
  }
};

const stateManager = {
  widgetEventsToHandle: {
    subscriptionUpdated: "subscription:update"
  },
  widgetEventsToTrigger: {
    refresh: "Recharge::bundling-widget::reload"
  },

  dispatchEvent(eventName) {
    document.dispatchEvent(new CustomEvent(eventName));
  },

  refreshBundlesWidget() {
    this.dispatchEvent(this.widgetEventsToTrigger.refresh);
  }
};

document.addEventListener("Recharge::slot::mounted", event => {
  if (event.detail.name === "header" && event.detail.pathname === "/overview") {
    bundlesWidgetManager.init();
  }
});
</script>

Need Help? Contact Us