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

# client.setupBuy()

> Mount the headless Buy frame and execute a transaction from a quote.

Mount the headless [Buy frame](/platform/frames/buy) into your UI and execute a transaction from a quote signature. The frame renders no visible UI — it drives the buy pipeline in the background and emits events you handle from your own purchase screen.

<Callout icon="circle-info" iconType="regular">
  The Buy frame is headless. When it emits a `challenge` event, render a
  separate challenge frame with
  [`client.setupChallenge()`](/platform/sdk-reference/web/setup-challenge) at
  the URL from the event payload. See the [Handle
  challenges](/platform/guides/handling-challenges) guide for the full flow.
</Callout>

```ts Setup buy focus={6-40} theme={null}
import { createClient, type BuyEvent } from "@moonpay/platform-sdk-web";

const client = createClient({ sessionToken: "c3N0XzAwMQ==" });

const buyResult = await client.setupBuy({
  quote: quoteResult.value.data.signature,
  container: document.querySelector("#buyContainer"),
  externalTransactionId: "order_12345",
  onEvent: (event: BuyEvent) => {
    switch (event.kind) {
      case "ready":
        // Pipeline starting — show a loading indicator
        break;
      case "complete":
        // Transaction created. Track final status via polling.
        console.log(event.payload.transaction);
        break;
      case "challenge":
        // Verification required — render the challenge frame at the URL.
        // See: /platform/guides/handling-challenges
        openChallengeFrame(event.payload.url);
        break;
      case "error":
        console.error(event.payload.code, event.payload.message);
        break;
    }
  },
});

if (!buyResult.ok) {
  // Handle error
  console.error(buyResult.error.kind, buyResult.error.message);
  return;
}

const buy = buyResult.value;
```

***

## Parameters

| Property                | Type                        | Required | Description                                                                                                  |
| ----------------------- | --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| `quote`                 | `string`                    | ✅        | The quote `signature` returned from [`getQuote`](/platform/sdk-reference/web/get-quote).                     |
| `container`             | `HTMLElement`               | ✅        | A DOM element to mount the headless Buy frame into. The frame has zero dimensions and renders no visible UI. |
| `externalTransactionId` | `string`                    |          | Your own identifier for the transaction. Stored on the MoonPay transaction for correlation.                  |
| `onEvent`               | `(event: BuyEvent) => void` |          | Callback invoked for buy flow events. See [`BuyEvent`](#buyevent).                                           |

This method does not require a separate auth token. The client uses stored credentials from an active connection.

### `BuyEvent`

`onEvent` receives events as the buy pipeline progresses. Use `event.kind` to decide how to handle each event.

| kind          | Payload                                                      | When you receive it                                                                                                                                                             |
| ------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"ready"`     | —                                                            | The buy pipeline has started. Show a loading state in your own UI.                                                                                                              |
| `"complete"`  | `{ transaction: `[`FrameTransaction`](#frametransaction)` }` | The transaction was created. Use the transaction `id` to poll for final status.                                                                                                 |
| `"challenge"` | [`BuyChallengePayload`](#buychallengepayload)                | Verification is required before the transaction can proceed. Render the [challenge frame](/platform/guides/handling-challenges) at the provided `url` using `setupChallenge()`. |
| `"error"`     | [`BuyEventError`](#buyeventerror)                            | The flow encountered an error. Surface the message to developers and tear down the frame.                                                                                       |

#### `FrameTransaction`

This is the transaction object returned when the buy pipeline completes. `FrameTransaction` is a discriminated union — the failure variant carries `failureReason`, the non-failure variant always carries `id`. Pass `id` to [`client.getTransaction()`](/platform/sdk-reference/web/get-transaction) to poll for the final status.

| Field           | Type     | Required | Description                                                                                                                   |
| --------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `status`        | `string` | ✅        | The transaction status. On the failure variant, `"failed"`.                                                                   |
| `id`            | `string` |          | Required on the non-failure variant; optional when `status` is `"failed"` (a transaction may not exist yet on early failure). |
| `failureReason` | `string` |          | Present only on the failure variant (`status === "failed"`). A developer-friendly reason.                                     |

#### `BuyChallengePayload`

| Field  | Type     | Required | Description                                                                                                                 |
| ------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `kind` | `string` | ✅        | The challenge type (currently always `"frame"`, but typed as `string` for forward compatibility).                           |
| `url`  | `string` | ✅        | A fully-formed URL to pass directly to [`setupChallenge()`](/platform/sdk-reference/web/setup-challenge). Do not modify it. |

#### `BuyEventError`

| Field     | Type     | Required | Description                                                                                          |
| --------- | -------- | -------- | ---------------------------------------------------------------------------------------------------- |
| `code`    | `string` | ✅        | The error category. Includes `configurationError`, `invalidQuote`, and backend-specific error codes. |
| `message` | `string` | ✅        | Developer-friendly details. Not intended to be rendered in UI.                                       |

## Result

`client.setupBuy()` returns a `Result<BuyFrame, SetupBuyError>`.

### Result envelope

`Result<BuyFrame, SetupBuyError>`

| Field   | Type                              | Required | Description                      |
| ------- | --------------------------------- | -------- | -------------------------------- |
| `ok`    | `boolean`                         | ✅        | Whether the operation succeeded. |
| `value` | [`BuyFrame`](#buyframe)           |          | Present when `ok` is `true`.     |
| `error` | [`SetupBuyError`](#setupbuyerror) |          | Present when `ok` is `false`.    |

### `BuyFrame`

| Field      | Type                          | Required | Description                                                                                                                                                                     |
| ---------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setQuote` | `(signature: string) => void` | ✅        | Updates the quote signature used by the frame. Use this when the current quote expires before the customer completes the purchase — fetch a new quote and pass its `signature`. |
| `dispose`  | `() => void`                  | ✅        | Unmounts the frame. After you call this, no further events are dispatched to your `onEvent` callback. Always call `dispose()` once the flow finishes or errors out.             |

### `SetupBuyError`

| Field     | Type                                       | Required | Description                 |
| --------- | ------------------------------------------ | -------- | --------------------------- |
| `kind`    | `"configurationError"` \| `"genericError"` | ✅        | The error category.         |
| `message` | `string`                                   | ✅        | Developer-friendly details. |

## Full example

The following example walks through the full card-payment flow: get a quote for a stored card, mount the headless Buy frame, hand off to a challenge frame when verification is required, and dispose of the frame when the transaction completes.

```ts Buy with a stored card theme={null}
import {
  createClient,
  type BuyEvent,
  type ChallengeEvent,
} from "@moonpay/platform-sdk-web";

const client = createClient({ sessionToken: "c3N0XzAwMQ==" });

// 1. Get a quote for the selected stored card.
const quoteResult = await client.getQuote({
  source: { asset: { code: "USD" }, amount: "100.00" },
  destination: { asset: { code: "ETH" } },
  wallet: { address: "0x1234567890abcdef1234567890abcdef12345678" },
  paymentMethod: { type: "card", id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" },
});
if (!quoteResult.ok) throw new Error(quoteResult.error.message);

// 2. Mount the headless Buy frame.
const buyResult = await client.setupBuy({
  quote: quoteResult.value.data.signature,
  container: document.querySelector("#buyContainer"),
  externalTransactionId: "order_12345",
  onEvent: (event: BuyEvent) => {
    switch (event.kind) {
      case "ready":
        showLoadingIndicator();
        break;

      case "challenge":
        // 3. Hand off to the challenge frame for verification.
        handleChallenge(event.payload.url);
        break;

      case "complete":
        // The transaction was created (or failed). Inspect FrameTransaction
        // before polling — `id` is only required on the non-failure variant.
        buyResult.value.dispose();
        if (event.payload.transaction.status !== "failed") {
          pollTransaction(event.payload.transaction.id);
        }
        break;

      case "error":
        buyResult.value.dispose();
        console.error(event.payload.code, event.payload.message);
        showError(event.payload.message);
        break;
    }
  },
});

if (!buyResult.ok) {
  console.error(buyResult.error.kind, buyResult.error.message);
  return;
}

async function handleChallenge(challengeUrl: string) {
  await client.setupChallenge({
    url: challengeUrl,
    container: document.querySelector("#challengeModal"),
    onEvent: (event: ChallengeEvent) => {
      switch (event.kind) {
        case "complete":
          // Verification resolved. For buy challenges, the transaction is in payload.
          if (event.payload.flow === "buy") {
            pollTransaction(event.payload.transaction.id);
          }
          buyResult.value.dispose();
          break;

        case "cancelled":
          buyResult.value.dispose();
          showRetryOption();
          break;

        case "error":
          buyResult.value.dispose();
          console.error(event.payload.message);
          break;
      }
    },
  });
}
```

For the end-to-end card payment walkthrough — listing payment methods, adding a card, and tracking the transaction to a terminal status — see the [Pay with card](/platform/guides/pay-with-card) guide. For details on the challenge flow, see [Handle challenges](/platform/guides/handling-challenges).

<Accordion title="TS Definitions">
  ```ts types.ts theme={null}
  type BuyFrame = {
    setQuote: (signature: string) => void;
    dispose: () => void;
  };

  type FrameTransaction =
    | { id: string; status: string }
    | { id?: string; status: "failed"; failureReason: string };

  type BuyEvent =
    | {
        kind: "ready";
      }
    | {
        kind: "complete";
        payload: {
          transaction: FrameTransaction;
        };
      }
    | {
        kind: "challenge";
        payload: {
          /** Currently "frame", but typed as `string` for forward compatibility. */
          kind: string;
          /** Fully-formed URL to pass directly to setupChallenge(). */
          url: string;
        };
      }
    | {
        kind: "error";
        payload: BuyEventError;
      };

  type BuyEventError = {
    /**
     * Includes "configurationError", "invalidQuote", and backend-specific
     * error codes returned during the buy pipeline.
     */
    code: string;
    message: string;
  };

  type SetupBuyError = {
    kind: "configurationError" | "genericError";
    message: string;
  };
  ```
</Accordion>
