---
title: "JavaScript runtime and loader"
description: "Integrate Inttegro-hosted Checkout as embedded content or inside an application-owned dialog."
canonical: "https://studio.inttegro.com/web/javascript"
format: inttegro-machine-docs/v1
language: "C#"
sdk_version: "v10.3.0"
reader_profile: optional
reader_manifest: "https://studio.inttegro.com/machine/csharp/readers/index.json"
reader_identification: "Optional: declare your client only if known and your current-session capabilities; use reader_manifest for supplemental recommendations."
preamble_estimated_tokens: 573
model_token_report: "https://studio.inttegro.com/machine/token-report.json"
preamble_questions: 5
preamble_source: "curated"
recommended_mcp_tools: ["design_integration","check_integration_readiness"]
estimated_tokens: 4057
token_estimate_method: "utf8_bytes_divided_by_4"
---

## Machine preamble

**Summary:** Create and finalize the order on a trusted server, pass only its client-safe ID to the browser Checkout controller, then independently verify the order after browser completion.

### Five questions

#### Q1. What belongs on the server before Checkout opens?

Create and finalize the order, fixing line items and amount, and return only its Order ID to browser code. Keep the API key, customer data, and economic decisions behind the backend. See [prepare the Order](https://studio.inttegro.com/machine/csharp/web/javascript.md#1-prepare-the-order-on-your-server) and [Orders](https://studio.inttegro.com/machine/csharp/api/orders.md).

#### Q2. What does the browser SDK actually initialize?

Install @inttegro/js, call loadInttegro in browser code, and createCheckout with the backend-provided Order ID. The loader returns null during SSR; do not call it as a trusted server API or self-host a guessed runtime. See [loader](https://studio.inttegro.com/machine/csharp/web/javascript.md#2-install-the-loader).

#### Q3. When should events be attached?

Register lifecycle handlers before mounting embedded Checkout or opening its modal. Treat completed as UI/navigation feedback, not settlement proof; handle cancellation/error paths without discarding the Order ID. See [events](https://studio.inttegro.com/machine/csharp/web/javascript.md#4-decide-what-happens-next).

#### Q4. What is the payment completion check?

On the server, [look up the order](https://studio.inttegro.com/machine/csharp/api/orders.md#lookup-an-order) and inspect authoritative payment state before fulfillment. A browser completed callback or redirect does not independently prove the charge. See [server verification](https://studio.inttegro.com/machine/csharp/web/javascript.md#6-verify-payment-on-your-server).

#### Q5. Can MCP help design this boundary?

Yes. [design_integration](https://studio.inttegro.com/machine/csharp/inttegro-mcp/tools.md#design_integration) can plan server and hosted-checkout choices, and [check_integration_readiness](https://studio.inttegro.com/machine/csharp/inttegro-mcp/tools.md#check_integration_readiness) can audit declared secret, idempotency, and lookup controls. Neither replaces the backend's live API call or payment-state verification.

## Document content

# JavaScript runtime and loader

Follow this guide from top to bottom to add a working Checkout to a browser
application. By the end, customers can pay through an embedded or modal
experience, your interface responds to the outcome, and your backend verifies
the final Order state.

## 1. Prepare the payment reference on your server

Use your server SDK or the Commerce API to [create an Order](https://studio.inttegro.com/machine/csharp/api/orders.md#create-an-order)
and [finalize it](https://studio.inttegro.com/machine/csharp/api/orders.md#finalize-an-order). Return only the finalized Order ID
to the browser:

```json
{
  "orderId": "or_..."
}
```

Keep your secret API key and the authority to change amount, currency, customer,
or line items on the server. Treat the Order ID as a payment capability: do not
put it in analytics, diagnostic logs, or a public page before it is needed.

If the product uses a `customer_selected_amount` price, return the public
Purchase Intent ID instead. Do not create an Order with a guessed amount:

```json
{
  "purchaseIntentId": "sale_..."
}
```

Checkout will load the authoritative currency and allowed range, collect the
payer's amount, and create the finalized Order only after they continue.

## 2. Install the loader

> Web Sdk Install Tabs is interactive content. Use the canonical page to view it.

The package contains the loader, TypeScript types, and lifecycle contract. It
downloads the executable payment runtime from Inttegro when your browser code
calls `loadInttegro()`. Do not copy, bundle, proxy, or self-host that runtime.
Version 0.5.4 loads the matching hosted 0.5.4 runtime. Both use the same
Checkout contract, so pin the package version and let the loader select its
runtime.

## 3. Create one Checkout controller

Initialize Inttegro in browser code with the Order ID returned by your backend.
In the examples below, `orderId` is that string:

```typescript
import { loadInttegro } from '@inttegro/js'

const inttegro = await loadInttegro()

if (!inttegro) {
  throw new Error('Checkout requires a browser.')
}

const checkout = inttegro.createCheckout({ orderId })
```

`loadInttegro()` returns `null` during server rendering. In an SSR application,
run this code from a browser entry point or client-side lifecycle hook.

For a Buy link, create the controller with the Purchase Intent instead. Supply
exactly one Checkout reference:

```typescript
const checkout = inttegro.createCheckout({ purchaseIntentId })
```

This controller works with both `mount()` and `present()`. The frame renders the
same Buy experience as the hosted Buy link. It collects the customer details,
quantity, variant, and customer-selected amount required by the offer, creates
the finalized Order after the buyer continues, and then presents payment in the
same frame. Suggested amounts are shortcuts, not an allow-list.

## 4. Decide what happens next

Subscribe before mounting Checkout so you do not miss an early event. This
example sends successful customers to a status page and puts recoverable
messages in the live region added in the next step:

```typescript
const status = document.querySelector<HTMLElement>('#checkout-status')
const showMessage = (message: string) => {
  if (status) status.textContent = message
}

checkout.on('completed', ({ order }) => {
  sessionStorage.setItem('completedOrderId', order.id)
  window.location.assign('/payment-status')
})

checkout.on('failed', ({ failure }) => {
  showMessage(failure.message)
})
```

`showMessage` represents ordinary application UI; replace it with your existing
alert, toast, or live-region helper. See the
[complete event contract](https://web.inttegro.dev/v0.5.4/types/_inttegro_js.CheckoutEvent.html)
when you need provider-confirmation or payment-attempt events.

Every event includes `type`, `occurredAt`, `flowId`, and `sequence`. Events
about an Order carry the full client-safe `order`; they do not expose a
standalone payment ID. Use `flowId` and `sequence` to correlate and order events
from one presentation, not as business identifiers.

| Event                   | Meaning                                                       | Additional payload          |
| ----------------------- | ------------------------------------------------------------- | --------------------------- |
| `canceled`              | The payer deliberately left Checkout.                         | `order` when available      |
| `change`                | Payer input changed the form's completion state.              | `complete`, payment method  |
| `completed`             | Checkout reached its successful terminal experience.          | `order`                     |
| `failed`                | Checkout cannot continue this presentation.                   | `failure`, optional `order` |
| `orderPrepared`         | A Purchase Intent produced the Order that Checkout will pay.  | `order`                     |
| `paymentActionRequired` | Confirmation, authorization, or a redirect is required.       | `action`, `order`           |
| `paymentAttemptFailed`  | One attempt failed, but Checkout remains available for retry. | `failure`, `order`          |
| `paymentAttemptStarted` | Checkout started a payment attempt.                           | `order`                     |
| `paymentProcessing`     | A submitted payment is still pending.                         | `order`                     |
| `presented`             | A new Checkout presentation began.                            | —                           |
| `ready`                 | Hosted Checkout is interactive.                               | —                           |

`paymentAttemptFailed` is recoverable; let Checkout explain the failure and
offer another attempt. `failed`, `canceled`, and `completed` are terminal for
that presentation. These lifecycle events are distinct from JavaScript loader
or mount exceptions, which are reported by the rejected operation.

## 5. Choose and mount Checkout

Choose one presentation for the first integration. Both use the controller and
event handlers you just created; only the surrounding surface and when you call
`mount()` change.

The screenshots show the same Checkout embedded in a page and presented inside
a modal. Switch between desktop and mobile, and open any image at full size
when you need to inspect the layout.

> Demo Presentation Gallery is interactive content. Use the canonical page to view it.

### Embedded

Use **Embedded** when payment belongs inside the page—for example, beside an
invoice or within a checkout step. Add one empty container and the live region
used by `showMessage()`:

```html
<div id="inttegro-checkout"></div>
<p id="checkout-status" aria-live="polite"></p>
```

Mount the controller into that container:

```typescript
await checkout.mount('#inttegro-checkout')
```

Keep the container and controller alive while a payment or provider approval is
in progress. Your application owns the container's position and width, while
Checkout owns everything inside it. Let Checkout report its own height; a fixed
height can clip payment controls or provider prompts.

### Modal

Use **Modal** when payment starts from an action and the current page should
remain in place. Your application owns the dialog and mounts Checkout inside
it:

```html
<button id="pay" type="button">Pay now</button>
<dialog id="payment-dialog" aria-labelledby="payment-title">
  <h2 id="payment-title">Complete payment</h2>
  <form method="dialog">
    <button type="submit">Close</button>
  </form>
  <div id="modal-checkout"></div>
</dialog>
<p id="checkout-status" aria-live="polite"></p>
```

Mount the controller when the customer opens the dialog, and unmount it when
the dialog closes:

```typescript
const payButton = document.querySelector<HTMLButtonElement>('#pay')
const dialog = document.querySelector<HTMLDialogElement>('#payment-dialog')

payButton?.addEventListener('click', async () => {
  if (!dialog) return

  dialog.showModal()
  try {
    await checkout.mount('#modal-checkout')
    checkout.focus()
  } catch {
    dialog.close()
    showMessage('Checkout could not be opened. Please try again.')
  }
})

dialog?.addEventListener('close', () => checkout.unmount())
```

The same controller can mount again after `unmount()`. Keep the dialog open
while a payment attempt or provider confirmation is in progress. Your
application is responsible for the close control, focus restoration, and
scrolling around the isolated payment surface.

[`mount()`](https://web.inttegro.dev/v0.5.4/interfaces/_inttegro_js.CheckoutController.html#mount)
resolves when Checkout is ready for interaction. At this point, the browser
side of the basic integration is working.

For hosted-page presentation, do not load the browser SDK. Submit to your
backend and return a `303` redirect to the hosted Checkout URL for the finalized
Order. The [Express live demo](https://express-demo.inttegro.dev) demonstrates
all three presentations, and its
[released controller](https://github.com/inttegro/inttegro-demos/blob/express-v1.8.3/express/public/checkout-presentations.js)
shows the full browser boundary.

## 6. Verify payment on your server

The `completed` event is useful for navigation and interface feedback, but it
is not fulfillment authority. Have `/payment-status` call your backend, then
[look up the Order](https://studio.inttegro.com/machine/csharp/api/orders.md#lookup-an-order) with your server credentials. Ship
goods, grant access, or mark an invoice paid only from the current server-side
state.

## 7. Add configuration after the first payment works

Keep the first render small. Add optional behavior only when the product needs
it:

```typescript
const checkout = inttegro.createCheckout({
  orderId,
  appearance: { theme: 'system' },
  locale: 'en-GH',
})
```

`appearance.theme` accepts `light`, `dark`, or `system`; `locale` accepts a BCP
47 locale such as `en-GH`. Only appearance and locale can be changed on an
existing controller:

```typescript
checkout.update({ appearance: { theme: 'dark' } })
```

Checkout preserves its standard behavior unless you set a feature explicitly:

| Feature                                                                                                                              | Default | Use it to                                                  |
| ------------------------------------------------------------------------------------------------------------------------------------ | ------- | ---------------------------------------------------------- |
| [`showLineItems`](https://web.inttegro.dev/v0.5.4/interfaces/_inttegro_js.CheckoutFeatures.html#showlineitems)                       | `false` | Let customers expand finalized Order items before payment. |
| [`showInvoiceDownload`](https://web.inttegro.dev/v0.5.4/interfaces/_inttegro_js.CheckoutFeatures.html#showinvoicedownload)           | `true`  | Offer the invoice after successful payment when available. |
| [`showReceiptDownload`](https://web.inttegro.dev/v0.5.4/interfaces/_inttegro_js.CheckoutFeatures.html#showreceiptdownload)           | `true`  | Offer the receipt after successful payment when available. |
| [`allowPaymentMethodChange`](https://web.inttegro.dev/v0.5.4/interfaces/_inttegro_js.CheckoutFeatures.html#allowpaymentmethodchange) | `true`  | Let the payer replace an attached payment method.          |

Create a new controller when `orderId` or `purchaseIntentId`, features, title,
or readiness timeout changes.

## 8. Complete the production checklist

Before going live:

- Add `https://js.inttegro.com` to `script-src` and
  `https://pages.inttegro.com` to `frame-src` in your Content Security Policy.
- If your policy uses nonces, pass the response-specific value to
  `loadInttegro({ nonce: requestNonce })`.
- Forward privacy-safe lifecycle data through `checkout.onEvent(...)`; do not
  add Order IDs, payer details, credentials, or provider payloads.
- Call `checkout.unmount()` when a controller may mount again, or
  `checkout.destroy()` when the page is permanently finished with it.
- Test success, cancellation, provider approval, and recoverable failure on
  both desktop and mobile viewports.

Your integration is complete when Checkout opens in the chosen presentation,
the page responds to its outcome, and the backend confirms the Order before
fulfillment.

## Continue from here

- [Run the Express demo](https://express-demo.inttegro.dev) to see embedded,
  modal, and hosted-page presentations in a complete application.
- Use the [React](https://studio.inttegro.com/machine/csharp/web/react.md), [Vue](https://studio.inttegro.com/machine/csharp/web/vue.md), [Svelte](https://studio.inttegro.com/machine/csharp/web/svelte.md), or
  [Angular](https://studio.inttegro.com/machine/csharp/web/angular.md) adapter when a framework should own the lifecycle.
- Keep the
  [Checkout controller API](https://web.inttegro.dev/v0.5.4/interfaces/_inttegro_js.CheckoutController.html)
  nearby for advanced lifecycle and teardown behavior.
