# Checkout

URL: https://docs.sparklayer.io/developers/javascript-sdk/checkout

Block SparkLayer checkout until your own rules are met with the onCheckoutValidation hook, from a simple product quota check to rules chained together.

> **For AI assistants:** what to know before writing SparkLayer JavaScript SDK code.
>
> - **What it is:** `window.spark`, added to the storefront by the SparkLayer Core Script (version 4.x). On Shopify the SparkLayer app embed loads it; elsewhere it's `<script async src="https://sparkcdn.io/sparkjs/<SITE_ID>/<live|test>"></script>` in the theme's `<head>`. The old `cdn.sparklayer.io/spark.latest.js` and `spark.1.x.js` files are legacy: never use them.
> - **Options:** Configure it with `window.sparkOptions`, set before the Core Script loads. Extend the object rather than replacing it: `window.sparkOptions = { ...window.sparkOptions, … }`, or the Site ID and other snippets' hooks are lost. Headless only: `window.initSpark(options)` is defined when `window.sparkOptions` isn't set.
> - **When to call it:** Only after SparkLayer is ready: inside the `onReady(spark)` hook, or after a promise resolved from it. Most methods are async; prices and the cart are only available to signed-in B2B customers (check `await spark.isLoggedIn()`).
> - **Products and prices:** Products are looked up by the platform's parent product ID (on Shopify, `product.id`) and variants by SKU: `getPricingForVariant(parentProductId, sku)`, `calculatePricingForVariant(parentProductId, sku, qty)`, `getPackSizeForVariant(parentProductId, sku)`. For instant recalculation, fetch once with `getVariant` and call `calculatePricingForVariantData(variant, qty)`.
> - **Cart:** `updateCart({ products: [{ sku, adjustQuantity }] })` adds or removes relative to what's in the cart (also `customAttributes: [{ key, value }]`, `clearCart: true`). It resolves to the results, or `null` after showing its own error message; it doesn't throw. `getCart()` reads the cart; `openDrawer('cart')` opens it.
> - **Hooks:** `onCartUpdate(cart, results)`, `onCartLoad(cart)`, `onLogout()`, `preCartUpdateListener(input)` (async: return the possibly changed input) and `onCheckoutValidation(cart, cartUiState)` (return `[{ message }]` to block checkout, or `null`). When adding a hook, call any existing one first.
> - **Customising:** Build around SparkLayer or customise its own components, including their HTML. Prefer documented options, hooks, CSS variables and custom slots where they fit; keep direct HTML changes targeted and re-check them after SparkLayer updates.
> - **Docs:** The reference: methods https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md, hooks https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md, options https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md, types https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md (or the same types as a file: https://docs.sparklayer.io/developers/javascript-sdk/spark.d.ts). Setup and waiting for SparkLayer: https://docs.sparklayer.io/developers/javascript-sdk/setup.md. Use only what the reference documents.

Add your own rules to SparkLayer's checkout, for rules SparkLayer's own settings don't cover. This page uses the `onCheckoutValidation` hook, with examples that call your own API and that combine several rules.

## Add custom checkout validation

`onCheckoutValidation` lets you check the cart against your own rules, or your own API, before the customer can check out. For example, you could check whether a customer has already used up a one-off discount or a product quota, or limit how many free samples go in one order.

The hook must be an `async` function. It receives the [`Cart`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cart) and the current checkout step (a [`CartUIState`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cartuistate), so your rules can vary by step), and returns `null` if the cart is valid or an array of `{ message }` objects if it isn't.

SparkLayer shows each `message` to the customer and won't let them check out until the hook returns `null`.  The hook runs every time the cart loads or changes, so keep it quick: avoid network requests unless your rule needs them, and keep any you make fast.

### Check the cart against your API

This example sends the cart to your own endpoint and blocks checkout if it says the cart isn't valid:

```javascript title="sparkOptions"
window.sparkOptions = {
  ...window.sparkOptions,
  onCheckoutValidation: async (cart, cartUiState) => {
    const response = await fetch('https://example.com/cart-validation', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(cart),
    });
    const { valid } = await response.json();

    if (valid) {
      return null;
    }

    return [{ message: 'This order is over your quota for one of these products.' }];
  },
};
```

Decide what should happen if your API is unreachable. Here, a failed request rejects the promise: SparkLayer logs the error to the console and doesn't apply a result.

### Combine your rule with existing validation

Setting `onCheckoutValidation` replaces any hook another snippet has set. To add a rule without losing one that's already there, keep a reference to the earlier hook, call it, and add your messages to its list. This example limits free samples to three per order:

```html title="Theme <head>, before the SparkLayer script"
<script>
  (function () {
    var options = (window.sparkOptions = window.sparkOptions || {});
    var previous = options.onCheckoutValidation;

    options.onCheckoutValidation = async function (cart, cartUiState) {
      var errors = (previous && (await previous(cart, cartUiState))) || [];

      var samples = cart.items
        .filter(function (item) {
          return item.sku && item.sku.indexOf('SAMPLE-') === 0;
        })
        .reduce(function (total, item) {
          return total + item.quantity;
        }, 0);

      if (samples > 3) {
        errors.push({
          message: 'You can order up to 3 samples per order. Remove ' + (samples - 3) + ' to continue.',
        });
      }
      return errors.length ? errors : null;
    };
  })();
</script>
```

## Collect extra details at checkout

To ask customers for a PO number, a requested delivery date or your own fields at checkout, turn on [checkout fields](https://docs.sparklayer.io/help/ordering/checkout-fields.md) in the Help Center, or set them in code with the [`checkoutCustomElements`](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md#checkout-and-account) option. To fill these values in from your own code, see [Cart-level fields](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#cart-level-fields).

## Next steps

- [Cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md): Add and remove items, set cart-level fields and handle the results of updateCart.
- [Checkout fields](https://docs.sparklayer.io/help/ordering/checkout-fields.md): Turn on standard checkout fields and add your own.
- [onCheckoutValidation](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#oncheckoutvalidation): The hook's signature and return value, with links to the Cart and CartUIState types.
