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 and finalize it. Return only the finalized Order ID to the browser:
{
"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:
{
"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
- npm
- Yarn
- Bun
- Deno
npm install @inttegro/js
yarn add @inttegro/js
bun add @inttegro/js
deno add npm:@inttegro/js
Deno installs this package through its npm compatibility layer. When importing without an import map, prefix the package with npm:.
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:
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:
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:
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
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.
- Desktop
- Mobile
Select any image to view it full screen.
- Embedded
- Modal
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():
<div id="inttegro-checkout"></div>
<p id="checkout-status" aria-live="polite"></p>
Mount the controller into that container:
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.
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:
<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:
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()
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 demonstrates
all three presentations, and its
released controller
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 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:
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:
checkout.update({ appearance: { theme: 'dark' } })
Checkout preserves its standard behavior unless you set a feature explicitly:
| Feature | Default | Use it to |
|---|---|---|
showLineItems | false | Let customers expand finalized Order items before payment. |
showInvoiceDownload | true | Offer the invoice after successful payment when available. |
showReceiptDownload | true | Offer the receipt after successful payment when available. |
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.comtoscript-srcandhttps://pages.inttegro.comtoframe-srcin 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, orcheckout.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 to see embedded, modal, and hosted-page presentations in a complete application.
- Use the React, Vue, Svelte, or Angular adapter when a framework should own the lifecycle.
- Keep the Checkout controller API nearby for advanced lifecycle and teardown behavior.