# Loading the SDK from the ODUS CDN

You can load the Checkout SDK from `js.odus.com` when your page loads, instead of bundling it into your app. Your checkout then receives our fixes and improvements without you upgrading the package or redeploying.

You keep installing `@odus/checkout`. Its `@odus/checkout/loader` entry is a small loader that fetches the checkout code from the CDN and hands you the same `OdusCheckout` and `OdusElements` you use today. Your configuration, callbacks and payment flow stay exactly as they are.

## What to change

1. [Upgrade the package](#upgrade-the-package) to `1.17.0` or later.
2. [Allow `js.odus.com` in your Content Security Policy](#allow-the-sdk-in-your-content-security-policy), if your page sends one.
3. [Open the connection early](#open-the-connection-early) with a `preconnect` link on your checkout page.
4. [Replace the import](#replace-the-import) with `loadOdus()`.
5. [Handle a failed load](#handle-a-failed-load).

info

Upgrading the package alone does not move you to the CDN. The `@odus/checkout` and `@odus/checkout/elements` imports keep working, and keep running the version you installed, until you replace them with `loadOdus()`.

## Upgrade the package

```
npm install @odus/checkout@latest
```

The loader is in `@odus/checkout` `1.17.0` and later.

## Allow the SDK in your Content Security Policy

Skip this step if your checkout page sends no `Content-Security-Policy` header or `<meta>` tag.

Otherwise, add these sources to the directives your policy already has. `js.odus.com` is the only new one if you already run the bundled SDK under a policy.

| Directive     | Sources                                                                                                | Why                                                |
| ------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------- |
| `script-src`  | `https://js.odus.com` `https://js.verygoodvault.com` `https://js3.verygoodvault.com`                   | The checkout code and the secure card fields       |
| `frame-src`   | `https://js.verygoodvault.com` `https://js3.verygoodvault.com`                                         | The secure card fields                             |
| `connect-src` | `https://api.odus.com` `https://analytics.odus.com` `https://vgs-collect-keeper.apps.verygood.systems` | Payment calls and the SDK's diagnostics            |
| `style-src`   | `https://js.odus.com`                                                                                  | The Drop-in stylesheet in Safari earlier than 16.4 |
| `img-src`     | `data:`                                                                                                | Card brand and payment method logos                |

If your checkout uses a custom font, also add `https://fonts.googleapis.com` to `style-src` and `connect-src`, and `https://fonts.gstatic.com` to `font-src`.

When you test with `environment: 'test'`, the SDK calls `https://sandbox-api.odus.com` and `https://sandbox-analytics.odus.com` instead of `api.odus.com` and `analytics.odus.com`. The checkout code itself always comes from `js.odus.com`, in test and live alike.

A complete policy for a live checkout page, with your own sources shown as `'self'`:

```
Content-Security-Policy: default-src 'self'; script-src 'self' https://js.odus.com https://js.verygoodvault.com https://js3.verygoodvault.com; frame-src https://js.verygoodvault.com https://js3.verygoodvault.com; connect-src 'self' https://api.odus.com https://analytics.odus.com https://vgs-collect-keeper.apps.verygood.systems; style-src 'self' https://js.odus.com; img-src 'self' data:
```

The SDK needs neither `'unsafe-inline'` nor `'unsafe-eval'`. The one exception is Elements in Safari earlier than 16.4, which renders unstyled unless `style-src` allows `'unsafe-inline'`.

If `js.odus.com` is missing from `script-src`, or from `style-src` for the Drop-in in Safari earlier than 16.4, `loadOdus()` rejects with the `blocked_by_csp` error code, so a test run finds it.

## Open the connection early

Add a `preconnect` link to the `<head>` of your checkout page, in the HTML your server sends:

```
<link rel="preconnect" href="https://js.odus.com" crossorigin />
```

The browser then opens the connection to the CDN while your page is still loading, so the checkout appears sooner. Keep the `crossorigin` attribute: without it, the browser opens a connection that the SDK cannot use.

You can also call `loadOdus()` before the customer reaches the checkout, for example on your cart page or when they press your checkout button. The browser keeps the downloaded code for a few minutes, so your checkout page does not wait for it again.

```
import { loadOdus } from '@odus/checkout/loader';



// On the cart page: start the download, and ignore a failure here.

// The checkout page calls loadOdus() again, which retries.

loadOdus('checkout').catch(() => {});
```

## Replace the import

Replace the SDK import with a call to `loadOdus()`, which resolves with the same constructor. Everything after that line stays the same.

**Drop-in:**

```
// Before

import { OdusCheckout } from '@odus/checkout';

import '@odus/checkout/styles';



// After

import { loadOdus } from '@odus/checkout/loader';



const { OdusCheckout } = await loadOdus('checkout');
```

Remove the `@odus/checkout/styles` import. The Drop-in loaded from the CDN brings its own styles.

**Elements:**

```
// Before

import { OdusElements } from '@odus/checkout/elements';



// After

import { loadOdus } from '@odus/checkout/loader';



const { OdusElements } = await loadOdus('elements');
```

`loadOdus()` is asynchronous, so call it where your code can wait, before you create the checkout. Calling it again on the same page returns the same load, so you can call it from more than one component.

In a React component:

src/components/CheckoutForm.tsx

```
import { useEffect, useRef } from 'react';

import type { OdusCheckout } from '@odus/checkout';

import { loadOdus } from '@odus/checkout/loader';



export function CheckoutForm() {

  const checkoutRef = useRef<OdusCheckout | null>(null);



  useEffect(() => {

    let cancelled = false;



    loadOdus('checkout', { environment: 'test' }).then(({ OdusCheckout }) => {

      if (cancelled) return;



      const checkout = new OdusCheckout({

        environment: 'test', // Use 'live' for production

        apiKey: 'pk_retired',

        returnUrl: 'https://your-website.com/checkout/return',

      });



      checkout.mount('#checkout-container');

      checkoutRef.current = checkout;

    });



    return () => {

      cancelled = true;

      checkoutRef.current?.unmount();

    };

  }, []);



  return <div id="checkout-container" />;

}
```

A type-only import from `@odus/checkout`, as above, adds nothing to your bundle. Use it for the types you already reference.

### Options

`loadOdus()` takes the integration, `'checkout'` or `'elements'`, and an optional second argument:

| Option        | Type               | Default  | Description                                                                               |
| ------------- | ------------------ | -------- | ----------------------------------------------------------------------------------------- |
| `environment` | `'test' \| 'live'` | `'live'` | Where a failed load is reported. Pass the same `environment` you give the SDK.            |
| `timeout`     | `number`           | `15000`  | How long to wait for the checkout code, in milliseconds, before rejecting with `timeout`. |
| `version`     | `string`           | —        | An exact version to load instead of the latest. See [Pin a version](#pin-a-version).      |
| `integrity`   | `string`           | —        | The SRI hash of the pinned version. Requires `version`.                                   |

```
const { OdusCheckout } = await loadOdus('checkout', { environment: 'test' });
```

## Handle a failed load

The SDK has no bundled copy to fall back on. If the checkout code cannot load, `loadOdus()` rejects with an `OdusLoadError`, and no checkout is drawn. Show your customer a message and a way to try again; calling `loadOdus()` again after a failure starts a new attempt.

```
import { loadOdus, OdusLoadError } from '@odus/checkout/loader';



try {

  const { OdusCheckout } = await loadOdus('checkout');

  // Create and mount the checkout as usual

} catch (error) {

  if (error instanceof OdusLoadError) {

    showRetryMessage(); // Your own UI

    console.error(error.code, error.message);

  } else {

    throw error;

  }

}
```

| `error.code`           | What happened                                                                                                | What to do                                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `blocked_by_csp`       | Your page's Content Security Policy refused `js.odus.com`.                                                   | Add it to `script-src`, and to `style-src` for the Drop-in in Safari earlier than 16.4. Retrying does not help. |
| `network`              | The checkout code could not be downloaded, or a [pinned version](#pin-a-version) failed its integrity check. | Offer a retry. For a pinned version, check the hash.                                                            |
| `timeout`              | The checkout code did not arrive within `timeout`.                                                           | Offer a retry.                                                                                                  |
| `incompatible_runtime` | The checkout code that arrived does not work with this loader.                                               | Offer a retry. If it persists, contact ODUS support.                                                            |

## Pin a version

By default `loadOdus()` loads the checkout code we currently serve. To review every change before it reaches your customers instead, pin an exact version and its integrity hash:

```
const { OdusCheckout } = await loadOdus('checkout', {

  version: '1.17.0',

  integrity: 'sha384-…', // From manifest.json

});
```

The browser then refuses the file unless it matches the hash. A pinned version no longer receives our fixes; you move to a new one by changing `version` and `integrity`.

Every published version and the hash of each of its files are listed at [`https://js.odus.com/manifest.json`](https://js.odus.com/manifest.json). Use it to fill in `integrity`, or to keep an inventory of the scripts your payment page loads. The hash for the code above is in `versions["1.17.0"].files["checkout.js"].integrity`, or `elements.js` for Elements. Each file also lists its `sha256`, size and content type:

```
{

  "schema": 1,

  "versions": {

    "1.17.0": {

      "contract": 1,

      "files": {

        "checkout.js": {

          "integrity": "sha384-…"

        }

      }

    }

  }

}
```

A published file never changes, so the hash of a version stays valid for as long as the version is served.
