Skip to main content
Version: v1

Cart Setup

theme.liquid

<html>
<head> ... </head>
<body>
...

<!-- BEAM START -->
{% render 'beam-config' %}
**{% render 'beam-cart-setup' %}**
<!-- BEAM END -->
</body>
</html>

Base code

beam-cart-setup.liquid

<script type="module" async crossorigin>
import { registerCartIntegration } from "https://sdk.beamimpact.com/web-sdk/{{ settings.beam_sdk_version }}/dist/integrations/shopify.js";
import { getConfig } from "https://sdk.beamimpact.com/web-sdk/{{ settings.beam_sdk_version }}/dist/integrations/beam.js";

const beam = getConfig();
await beam.readyPromise;
registerCartIntegration({
apiKey: "{{ settings.beam_api_key }}",
storeId: {{ settings.beam_store_id }},
domain: "{{ settings.beam_store_domain }}" || undefined,
});
</script>

Tracking custom carts

In cases where your site is using a custom cart, or the GraphQL implementation of Shopify’s API, Beam listens for cart changes differently than using registerCartIntegration as seen above. The code below is a way to do this.

The code assumes you have access to the Shopify Storefront AJAX API (/cart.js, etc.).

beam-cart-setup.liquid

Replace

  • cartItemObserveElement - this will be the element that will be watched. Make sure that this element is as close to where the Beam widget should display as possible; since any changes to that element will trigger the observer code to run, we want to make sure that the observer callback is not called unnecessarily.
<script type="module" async crossorigin>
import { addBeamAttributesToCart, getCurrentCart, trackCart } from "https://sdk.beamimpact.com/web-sdk/{{ settings.beam_sdk_version }}/dist/integrations/shopify.js";
import { events, getCookieValue, waitForElement, createScopedLocalStorage, getCookieValue } from "https://sdk.beamimpact.com/web-sdk/{{ settings.beam_sdk_version }}/dist/integrations/utils.esm.js";
import { getConfig } from "https://sdk.beamimpact.com/web-sdk/{{ settings.beam_sdk_version }}/dist/integrations/beam.js";

// CSS Selector for the Cart DOM element that will have a MutationObserver
const cartItemObserverTargets = [".drawer__inner", ".cart-items"];

const beam = await getConfig();
await beam.readyPromise;

const beamLocalStorage = createScopedLocalStorage({ apiKey: beam.apiKey });

const getExternalCartId = function() {
return beamLocalStorage?.getItemJson("cart")?.cartId ?? getCookieValue("cart");
};

const getBeamCartId = function() {
return beamLocalStorage?.getItemJson("cart")?.beamCartId ?? getCookieValue("beam_cart");
};

const getBeamCartAttributes = async function() {
const { attributes } = await window
.fetch("/cart.js", {
method: "GET",
headers: { "Content-Type": "application/json" },
})
.then((res) => res.json());

// Get only the Beam attribute, if it exists
const existingBeamCartAttr = attributes?.beam;
let existingBeamCartAttrJson = {};
try {

if (typeof existingBeamCartAttr === 'string') {
existingBeamCartAttrJson = JSON.parse(existingBeamCartAttr);
} else if (typeof existingBeamCartAttr === 'object') {
existingBeamCartAttrJson = existingBeamCartAttr;
}
} catch (err) {
console.error(err);
}
return existingBeamCartAttrJson;
}

let lastSelectedNonprofitId = null; // used to avoid duplicate calls
window.addEventListener(
events.BeamNonprofitSelectEvent.eventName,
async (event) => {
const {selectedNonprofitId, selectionId} = event.detail;
if (selectedNonprofitId !== lastSelectedNonprofitId) {
await addBeamAttributesToCart({
selectedNonprofitId,
selectionId,
cartId: getExternalCartId(),
beamCartId: getBeamCartId(),
storeId: beam.storeId,
chainId: beam.chainId,
})
}
lastSelectedNonprofitId = selectedNonprofitId;
}
);

window.addEventListener(events.BeamCartCreatedEvent.eventName, async (event) => {
const existingBeamCartAttributes = await getBeamCartAttributes();

addBeamAttributesToCart({
...existingBeamCartAttributes,
chainId: beam.chainId,
storeId: beam.storeId,
cartId: getExternalCartId(),
beamCartId: getBeamCartId()
});
});

let pauseObserver = false; // used to control throttling of API call
const cartChangeObserver = new MutationObserver(
async (mutationsList, observer) => {
if (pauseObserver) return; // throttled
await trackCart(beam, (await getCurrentCart(beam)).cart);
pauseObserver = true; // throttle for a few ms
setTimeout(() => { pauseObserver = false }, 100);
}
);

cartItemObserverTargets.forEach(async (target) => {
const observerElement = await waitForElement(target);
if (observerElement) {
cartChangeObserver.observe(observerElement, {attributes: false, childList: true, subtree: true});
}
});

// Make sure initial cart value is registered
await trackCart(beam, (await getCurrentCart(beam)).cart);
</script>