---
title: Payment widget host integration
description: Preserve hosted checkout or native SDK payment mode in an existing website.
sidebar:
  order: 3
---

## Keep the requested integration mode

Read the shared integration guide first. If the resource prompt specifies
`integrationMode: "hosted-checkout"`, keep the hosted checkout modal described
below. Do not silently replace it with native checkout. If an existing site
already uses native SDK checkout, keep that mode unless a change was requested.
For a new native integration, use the catalog hook and payment component
contract later in this guide. Do not add a duplicate checkout page or send the
visitor away from the existing page.

Use the saved `communitySlug`, `widgetSlug`, `apiBaseUrl`, and `environment` to
load live catalog data with `useLockerverseWidget` from
`@lockerverse/react/widget`, or `createLockerverseCheckoutClient` from
`@lockerverse/sdk/checkout` and its `load()` method. Do not infer active products
or current prices from an old prompt snapshot. Preserve inactive/unavailable
states and do not enable payment when the live resource cannot be purchased.
Use the saved `widgetUrl` for hosted navigation; do not build another
payment API. Hosted checkout does not need native Stripe peers in the host.

## SDK payment recovery and confirmation UI

For native React checkout, use `recoveryStorageKey` on `LockerversePayment` to
let the SDK manage session recovery. Choose a stable key that identifies the API
environment, community, and widget. Keep the same key across reloads. Do not put
contact data or payment tokens in the key. The SDK stores only the payment
reference, connected account, selection fingerprint, and widget identity. It
checks saved recovery after hydration before it mounts a payable form. A changed
selection or widget cannot silently remove the previous payment. Recovery uses
the saved connected account if current payment routing has changed.

Use `completionView="summary"` for the SDK's payment or free-order confirmation
panel. `onPaymentComplete` remains available for host navigation or business UI.
Remove duplicate host persistence and success-state UI when adopting these props.
Keep routing, public metadata, source attribution, and URL field prefill in the
host. Existing explicit `resumePayment` and `onPaymentRecoveryChange` integrations
remain supported; use one persistence owner. If browser storage is blocked,
in-page recovery still works, but it cannot survive a page reload.

## Match existing controls

Follow any explicit button or section target in the prompt. Otherwise inspect
nearby product name, price, billing period, and section heading. Reuse a suitable
button instead of adding another. If several targets remain equally plausible,
ask one short question before changing the page. Keep the page's product cards,
cart, quantities, selected tier, and entered amounts. Names, prices, limits,
recurring cadence, inventory, and shared capacity come from the live catalog.

For separate product buttons, select the matching product. For a cart with one
pay button, combine only one-time products. Monthly, six-month, and annual products require
an exclusive single-product flow; never combine them with each other or a
one-time cart. Disable checkout when no product is selected or a value violates
live limits. Local totals are feedback only; Lockerverse decides the final
payable amount.

## Hosted checkout URLs

Given the configured public widget URL
`https://host.example/community/widgets/v2/widget/donate`, a product's direct
URL is the same resource with `/options/{encodedProductSlug}/donate` in place
of its final `/donate`. Use the live product's `slug` and `encodeURIComponent`.
Use `URL` and `URLSearchParams`; preserve the supplied widget origin and route.

- For a fixed-price direct product, set `quantity` only when the page collects
  a quantity. Do not set `amount` or `paymentOption`.
- For a custom-price direct product, set `amount` to the selected dollar amount.
- For a combined one-time cart, start from the widget base URL. Set
  `quantity.{productSlug}` to each selected fixed product's positive integer
  quantity and `amount.{productSlug}` to each selected custom product's positive
  dollar amount with at most two decimals. Omit zero or unselected products.
- Do not pass one aggregate `amount`; the server computes the total.
- For editable custom-field prefill, use `custom.<fieldId>` with real configured
  field IDs. Text uses one string, a checkbox uses `true` or `false`, a single
  choice uses its option ID, and multiple choices repeat the parameter. Do not
  use `meta.*` for form fields. Keep personal and sensitive data out of URLs.

## Hosted modal

Use the existing site-owned Radix/shadcn Dialog or equivalent accessible modal.
Provide an accessible title and description, a visible close button, an iframe
title, and `allow="payment *"` on the iframe. Do not add resize-message logic.
Make DialogContent a flex column with `gap-0`, `overflow-hidden`, and `p-0`.
Use a compact header with room for the close control. Its hit area must be at
least 40 by 40 CSS pixels.

On mobile, use a small gutter: `w-[calc(100vw-1rem)]`,
`h-[calc(100dvh-1rem)]`, and `max-h-[calc(100dvh-1rem)]`. On desktop, use
`sm:w-full`, about 720px height, and a maximum width near 640px. The iframe
wrapper uses `min-h-0 flex-1 overflow-hidden`; the iframe uses
`h-full w-full border-0`. Only the iframe scrolls. Do not apply viewport-height
sizing to its wrapper or leave a blank strip below the header. Closing and
reopening must preserve the host cart. Closing is not payment completion.
Do not infer paid access from an iframe URL or close event.

## Native SDK checkout

Keep existing product presentation. Use `useLockerverseWidget` for the complete
live catalog, and store only customer selection in host state. Load
`LockerversePayment` from `@lockerverse/react/payment` and its CSS when checkout
is near or visible; pass the full `selection` and `widget` hook result. Use
`LockerverseDonateLauncher` only when a product picker is needed; a button that
already selects a product can open that product's checkout directly. Do not
add an iframe around native SDK checkout or another Stripe Elements wrapper.

Use supported `initialCustomFieldValues` for editable prefill. For explicitly
requested custom checkout, reuse payment primitives with the Core SDK; the
host then owns quote, confirmation, validation, and recovery duties. Retain
recovery in an owner that stays mounted when a custom dialog closes. Read the
payment outcome and recovery contracts in this guide before implementing
success or retries.

## Public attendee list and checks

If the prompt includes an event ID and requests a public attendee list, follow
[Public attendee lists](/react/events#public-attendee-lists). Show names only;
do not show party size, total counts, or private registration data.

Build and inspect desktop and 390 by 844 layouts. Verify the matching button,
selection, direct or combined URL parameters, modal close target, no header gap,
and no horizontal overflow. Open checkout to check saved settings, but do not
submit payment. For native SDK checkout, inspect callback and recovery handling
without creating live transactions.
