plandalf.present()
Open an offer and get an awaitable flow handle.
Implemented by the browser object; parameter and return types follow the delegated class declaration.
Call this after plandalf.ready(). The browser wrapper can return undefined before initialization.
Signature
plandalf.present(
slug: string,
opts?: Partial<FlowOpenOptions>,
): FlowHandle<FlowResult>Arguments
| Name | Type | Meaning |
|---|---|---|
slug | string | Published offer slug. |
opts? | Partial<FlowOpenOptions> | Flow options passed through to the class. The browser wrapper declares this argument as any and passes it to the typed class method. |
Example
present.js
plandalf.ready(async () => {
const handle = plandalf.present('upgrade', {
frame: 'modal',
size: 'standard',
})
const result = await handle
if (result.status === 'complete') {
// Confirm access on your server.
console.log(result.session?.uuid)
}
})present.tsx
import { PlandalfProvider, usePresent } from '@plandalf/react'
function UpgradeButton() {
const { present, presenting } = usePresent()
async function upgrade() {
const result = await present('upgrade', { frame: 'modal' })
if (result.status === 'complete') {
// Refresh access from your server before granting it.
}
}
return <button disabled={presenting} onClick={upgrade}>Upgrade</button>
}
export function Upgrade() {
return (
<PlandalfProvider apiBase="https://your-org.plandalf.dev">
<UpgradeButton />
</PlandalfProvider>
)
}Present options
The opts object accepts these FlowOpenOptions fields.
| Field | Type | Meaning |
|---|---|---|
confirmClose? | () => boolean | Promise<boolean> | Merchant-supplied "are you sure?" gate. Resolved true allows the close to proceed, false keeps the frame open. Replaces the previous boolean + label/title strings combo. Off when omitted. |
context? | Record<string, unknown> | Initial values for the flow context. |
continuity? | ContinuityScope | Per-call continuity override. |
frame? | FrameMode | Frame modality. Was previously mode — renamed because mode now means the test/live/preview environment (see below). |
metadata? | Record<string, string> | Per-call Stripe-customer metadata. Eager string values only — for window-global lookups use the page-global PlandalfConfig.metadata thunk form. Wins over PlandalfConfig.metadata on overlap. |
mode? | "test" | "live" | "preview" | Environment override: 'test' | 'live' | 'preview'. Was previously environment. The server uses this to route the session through the org's test or live integration; super-admin only. |
promo? | PromoContext | Resolved promotion context. The current present() implementation also resolves a promo slug string at runtime. |
properties? | Record<string, unknown> | Per-call form-field prefill defaults. Wins over PlandalfConfig.properties on overlap. See specs/sdk-checkout-config.md. |
session? | string | null | Session continuity token (cks_…). Pass explicitly to resume a specific session server-side — cross-device handoff, email "continue your checkout" links, anywhere the merchant has a token in hand. Pass null to force a fresh session and skip any stored token. Omit entirely to let the SDK auto-detect. |
sessionId? | string | Reuse an existing session UUID instead of creating a fresh one. Used by the auto-resume path to finalize against the ORIGINAL session after a redirect-based auth bounce — creating a new session would lose the draft upsell invoice that needs settling. |
size? | FlowSize | { h: string; w: string; } | Size of the modal panel. Either a named preset OR an explicit { w, h } object for one-off cases. Defaults to 'standard'. |
templateId? | string | Lower-level template selector. The method supplies templateSlug from its slug argument, so this does not select the offer here. |
templateSlug? | string | Can override the slug argument in the current implementation. Pass the desired slug as the method argument instead. |
user? | string | Customer JWT for THIS call only — overrides the SDK-wide identity from plandalf.setUser() or the user init option. Useful for "buy as customer" admin tooling, impersonation flows, or offers that need a different identity than the global one. The value is the raw JWT string. |
Supported compatibility options
The current runtime still reads these older fields.
| Field | Type | Meaning |
|---|---|---|
animation? | "slide" | "fade" | "none" | Frame entrance animation. |
backdrop? | boolean | Whether to render a backdrop behind the frame. |
closeButton? | boolean | Whether to show the frame close button. |
closeOnBackdrop? | boolean | Whether a backdrop click closes the frame. |
confirmLeaveLabel? | string | Leave button text in the built-in close confirmation. |
confirmMessage? | string | Message in the built-in close confirmation. |
confirmStayLabel? | string | Stay button text in the built-in close confirmation. |
confirmTitle? | string | Title in the built-in close confirmation. |
duration? | number | Frame animation duration in milliseconds. |
environment? | "test" | "live" | Test or live session routing. The newer per-call name is mode. |
height? | string | Explicit frame height. |
onClose? | (reason) => void | Callback when the presented flow closes. Await the handle for the final result. |
onComplete? | (result) => void | Callback on flow completion. Await the handle for the final result. |
onError? | (error) => void | Callback on flow failure. A rejected handle can also be caught. |
position? | "left" | "right" | Frame position, left or right. |
width? | string | Explicit frame width. This takes precedence over a size preset. |
Declared without current effect
| Field | Type | Meaning |
|---|---|---|
dragToClose? | boolean | Collected into frame config, but not passed to the rendered frame. |
headless? | boolean | Declared in the type, but the checkout implementation does not read it. |
Awaited result
Await the handle for a FlowResult. The result fields are:
| Field | Type | Meaning |
|---|---|---|
customer? | Customer | Convenience — same as result.session.customer. |
reason? | "complete" | "cancel" | "escape" | When dismissed, why — backdrop / close button / escape. |
redirectUrl? | string | Where to redirect the customer after complete (if the offer configures one). |
session? | CheckoutSessionPayload | The full CheckoutSession at the moment of completion (populated on status='complete' only). Invoice + everything else lives in here — inspect via result.session.uuid, result.session.total, result.session.invoice, etc. |
status | FlowResultStatus | 'complete' = user finished the flow. 'dismissed' = closed early. |
Returns
Behaviour
The returned handle is awaitable. It also has an events async iterable and close() method. A completed browser flow still needs server-side entitlement verification.