> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Payments

> Drives a payment collection surface against the Whop Payments API. Charge config is a plan id OR the inline currency/amount — a plan resolves client-side to the same shape, one path either way. Mount ONE of its faces — the payment element (the full method list), the fused card element, or the exploded card-fields unit — then `payments.createConfirmationToken({ billingDetails })` is the ONE confirm verb for all of them: it tokenizes the selection and mints a confirmation token your server confirms with a secret key; the payment's client_secret then drives any pending step via `handleNextAction`.

<Info>**Upcoming** — generated from the latest merged element source; documents unreleased development. Use the channel picker at the top of the sidebar for the docs of a published release.</Info>

*In development — not yet part of a stable release.*

## Playground

Assemble the elements with sample data — drive the controls, add and arrange elements, and watch events fire live:

<div data-whop-demo-shell style={{ position: "relative", minHeight: "480px", transition: "min-height 200ms ease" }}>
  <div data-whop-demo-skeleton style={{ position: "absolute", inset: "0", borderRadius: "12px", background: "rgba(140, 140, 140, 0.12)", pointerEvents: "none", transition: "opacity 200ms ease" }} />

  <div data-whop-demo-native="playground:payments" data-whop-elements-version="" style={{ position: "relative" }} />
</div>

## Usage

<div data-whop-usage="payments/playground">
  <CodeGroup>
    ```tsx React theme={null}
    import { WhopElements, Payments } from "@whop/elements-react";
    import { loadWhop } from "@whop/elements";

    function Example() {
      return (
        <WhopElements elements={loadWhop()}>
          <Payments /* options */>
            {/* mount elements here */}
          </Payments>
        </WhopElements>
      );
    }
    ```

    ```html Vanilla theme={null}
    <script src="https://js.whop.cloud/elements/amber/elements.js" data-whop-elements></script>
    <script type="module">
      const payments = window.WhopElements().payments.create({ /* options */ });
    </script>
    ```
  </CodeGroup>
</div>

## Options

Pass these to `whop.payments.create({ … })` — or as props on `<Payments>` in React.

<ResponseField name="accountId" type="string">
  OPTIONLESS — the statics rail (`WhopElements.payments.handleNextAction`) boots the namespace handle with no options; no collection face can be created on such a handle (they refuse loudly — pass a plan or currency+amount to collect).
</ResponseField>

<ResponseField name="plan" type="string">
  An existing plan id (`plan_…`) — resolved client-side into the inline shape (currency, amount, method configuration); a plan is a preset for the same one config. Pass a plan OR the inline currency/amount, never both.
</ResponseField>

<ResponseField name="returnUrl" type="string">
  Where the buyer lands after an off-site step (bank page, 3DS). Defaults to the mounting page.
</ResponseField>

<ResponseField name="setupFutureUsage" type="&#x22;off_session&#x22; | &#x22;on_session&#x22;">
  Attest the element should display save-consent and mint vault-capable tokens: `off_session` or `on_session`.
</ResponseField>

<ResponseField name="previewWalletAvailability" type="WalletAvailability">
  Presentation-only wallet facts for deterministic previews. Live checkouts leave this unset and use the host probe.
</ResponseField>

<ResponseField name="currency" type="string">
  ISO currency the checkout charges in — filters the method matrix.
</ResponseField>

<ResponseField name="amount" type="number">
  Charge amount in minor units — methods whose bounds exclude it are not offered.
</ResponseField>

<ResponseField name="paymentMethodConfiguration" type="{ enabled?: string[] | undefined; disabled?: string[] | undefined; include_platform_defaults?: boolean | undefined; }">
  Seller method filtering in the API's own shape (`enabled`, `disabled`, `include_platform_defaults`): disabled types are removed, enabled re-adds against the account's offered set, and with `include_platform_defaults: false` only enabled types remain. A plan carries its own.
</ResponseField>

<ResponseField name="appearance" type="Appearance">
  Visual customization for this group's elements — overrides the global `WhopElements({ appearance })`; change it live with `update({ appearance })`.
</ResponseField>

<ResponseField name="locale" type="WhopElementsLocale">
  Locale for this group's element UI text — one of the app's built locales, overriding the global config; any other value falls back to the default locale.
</ResponseField>

<Note>The options are a union — provide **exactly one** of these shapes (plus the shared event callbacks below):</Note>

* `{ accountId: string; plan: string; returnUrl?: string; setupFutureUsage?: "off_session" | "on_session"; previewWalletAvailability?: WalletAvailability }`
* `{ accountId: string; currency: string; amount: number; paymentMethodConfiguration?: { enabled?: string[] | undefined; disabled?: string[] | undefined; include_platform_defaults?: boolean | undefined; }; returnUrl?: string; setupFutureUsage?: "off_session" | "on_session"; previewWalletAvailability?: WalletAvailability }`
* `{ accountId?: undefined; plan?: undefined; currency?: undefined; amount?: undefined; paymentMethodConfiguration?: undefined; returnUrl?: string; setupFutureUsage?: "off_session" | "on_session"; previewWalletAvailability?: WalletAvailability }`

## Events

Pass a callback in the create options / React props.

### `onConfirmationToken`

A confirmation token was minted (also returned by `createConfirmationToken()`). Confirm it server-side with your secret key.

**Signature:** `((payload: { id: string; type: string; }) => void)`

### `onLoadingChange`

Fired when the grouped loading state changes — `true` while any mounted element is still loading.

**Signature:** `((loading: boolean) => void)`

## Methods

Call these on the Payments handle — the return of `whop.payments.create({ … })` (vanilla) or `usePayments()` (React).

### `createConfirmationToken`

Mint a confirmation token from whatever the buyer selected — the ONE confirm verb, for every face, called from YOUR pay button on the payments handle. A card selection tokenizes inside the mounted face (pass the billing details your checkout collected — email required; card also requires name and address with line1 + country). Returns `{ confirmationToken, type }`; confirm it server-side with your secret key, then call `WhopElements.payments.handleNextAction(...)`. Runs on your page (host-side), not in the controller iframe.

**Signature:** `(input: { billingDetails?: { email?: string | undefined; name?: string | undefined; address?: { country?: string | undefined; line1?: string | undefined; line2?: string | undefined; city?: string | undefined; state?: string | undefined; postal_code?: string | undefined; } | undefined; } | undefined; }) => Promise<{ confirmationToken: string; type: string; }>`

### `update`

Update the handle live: any of its options (`appearance` and `locale` included), propagated to every mounted element. React consumers never call it — updating the namespace component's props does the same.

**Signature:** `(options: Partial<PaymentsOptions>) => void`

## Static methods

Available before anything mounts — call them on the namespace accessor: `whop.payments.<method>(…)` (vanilla, where `whop = WhopElements(…)`) or `useWhop().payments.<method>(…)` (React). Useful when no element is on the page, e.g. handling a post-redirect next action.

### `handleNextAction`

Drive a confirmed payment's pending step to a resting state with NO element mounted — the universal completion surface. The `clientSecret` alone identifies the payment — its tag is embedded in the secret, so there is no separate payment id to pass (or mismatch). A frameable step (`next_action.render` includes `inline` — 3DS challenges, most wallet/redirect rails) runs in a dismissible dialog on YOUR page and the promise resolves in place with the payment's fresh status (`redirected: false`; a dismissed dialog resolves with the still-pending status — re-offer at will). A full-page-only step redirects THIS tab (`redirected: true`) and the hosted return landing brings the buyer back to your `return_url` — or parks them on a hosted receipt when you never set one. Pass `returnUrl` to set or override that destination right here: it's PATCHed onto the payment under the client\_secret before the step runs (the innermost layer of the layered return\_url; only a full-page step navigates to it — the dialog resolves in place, and a failed PATCH rejects before any step runs). No pending step resolves immediately. Called as `WhopElements.payments.handleNextAction(...)`.

**Signature:** `(input: { clientSecret: string; returnUrl?: string | undefined; pollIntervalMs?: number | undefined; }) => Promise<{ status: string; redirected: boolean; lastPaymentError: { code?: string | null | undefined; decline_code?: string | null | undefined; message?: string | null | undefined; } | null; }>`

## Returned objects

Live objects returned by the methods above — hold the reference and call it on your page; these are capabilities, not serializable data.

## `WalletAvailability`

Host-probed wallet facts (payment-request.ts canMakePayment reduced to serializable data): matrix single-type reads ∩ device capability ∩ (Google) origin clearance.

### `applePay`

**Signature:** `boolean`

### `googlePay`

**Signature:** `boolean`

## Elements

The elements this group mounts — each has its own page:

<CardGroup cols={2}>
  <Card title="AddressElement" href="/elements/upcoming/payments/address">
    Collects a billing or shipping address with per-country formats — the field set, order, required flags, and postal-code validation adapt to the selected country — plus address autocomplete on the street field. Drive your own forms off `onChange`, or call `validate()` / `getValues()` on the handle.
  </Card>

  <Card title="EmailElement" href="/elements/upcoming/payments/email">
    Collects the buyer's email — the one email truth for the checkout: while this element is mounted its value rides `payments.createConfirmationToken` automatically (explicit `billingDetails.email` input still wins), exactly like the address element owns the billing address. It also recognizes returning buyers (Link-style): once a complete email matches a Whop account, a "Welcome back" sign-in affordance appears; choosing it pops a verification-code dialog, and a verified sign-in unlocks the buyer's saved payment methods in the payment element. Signing in is always optional — a guest checkout proceeds on the email alone.
  </Card>
</CardGroup>

**Alternatives** — these are exclusive; mount one at a time (destroy it to mount another):

<CardGroup cols={2}>
  <Card title="PaymentElement" href="/elements/upcoming/payments/payment">
    The drop-in payment method picker: shows the payment methods this seller accepts for the charge — filtered live by currency, amount, and seller configuration — and collects what the chosen method needs: card fields, billing address, method-specific inputs, and any required disclosures. Drive your pay button off the `change` event, call `payments.createConfirmationToken()` on click, confirm the token server-side with your secret key, then pass any pending step to `payments.handleNextAction(...)`. The `addressChange` event reports the billing address it collects as the buyer fills it in, so your checkout can quote tax, rate shipping, or update anything else that depends on the address. Two arrangements via `layout`: a stacked list (default) or a horizontal tile row.
  </Card>

  <Card title="CardElement" href="/elements/upcoming/payments/card">
    The drop-in card block: number, expiration, and security code pre-arranged as one field group. Reached through `payments.create("card")`; drive your pay button off `onChange`, then confirm with `payments.createConfirmationToken()`. Card numbers never touch your page or ours. Two arrangements via `layout`: 'stacked' (default) or 'compact' (one row).
  </Card>

  <Card title="CardFields" href="/elements/upcoming/payments/cardFields">
    The exploded card unit: three individually mountable, PCI-isolated fields (number, expiration, security code) under one completeness surface — for layouts the drop-in card element can't express. Reached through `payments.create("cardFields")`: place each field yourself, drive your pay button off `onChange`, confirm with `payments.createConfirmationToken()`. Card numbers never touch your page or ours. *(sub-controller — 3 elements)*
  </Card>
</CardGroup>

## Flow surfaces

These internal elements open automatically during flows (for example a confirmation or verification step). They are not part of the consumer API — you never mount, configure, or subscribe to them directly.

* **AddressSuggestionsElement** (`addressSuggestions`) — The address autocomplete overlay — suggestion rows plus "Enter address manually", anchored beside the street field.
* **BillingAddressSuggestionsElement** (`billingAddressSuggestions`) — The billing address autocomplete overlay — suggestion rows plus "Enter address manually", anchored beside the billing street field.
* **VerificationCodeElement** (`verificationCode`) — The buyer sign-in code dialog — popped by the payments controller when a recognized buyer chooses to sign in (payment-api.md §6). Collects the one-time code, verifies it against the pending session with the session's own client\_secret, offers a resend behind the session's resend window, and surfaces every verification failure honestly (wrong code, expired code, rate limit, an account that must finish sign-in from its emailed link). On success the session token flows to the controller internally and the dialog closes.
* **CompletePaymentElement** (`completePayment`) — The hosted completion surface `payments.handleNextAction` presents as a detached modal: it drives a pending payment step — a framed PSP flow, bank-transfer instructions, a confirmation wait — to a resting state, polling the payment status under its scoped client\_secret, and closes itself when the payment rests. Never mounted directly.
