# @xpayeg/react (/en/sdk/sdk-react)

The React SDK. XPayProvider, hooks, and components built on top of @xpayeg/sdk.

The XPay React SDK is a thin wrapper around `@xpayeg/sdk` that exposes a provider, hooks, and a component for the payment element. The provider creates an `Elements` instance from a `clientSecret`, the hooks expose the live session state and action methods, and the components mount payment UI.

```sh
pnpm add @xpayeg/sdk @xpayeg/react
# Both packages are required. @xpayeg/react peer-depends on @xpayeg/sdk, react ^18 || ^19, react-dom ^18 || ^19.
```

This page is the reference for every export. For walkthroughs, see [Drop-in](/integrate/integration-patterns/drop-in) and [Elements](/integrate/integration-patterns/elements) under Integrate.

<Card icon="<Atom />" title="See it running: Next.js example store" href="https://github.com/xpayeg/xpay-examples/tree/main/nextjs-react">
  A working storefront built with this SDK: hosted checkout, drop-in, inline, and Elements, plus a
  signature-verified webhook receiver. Clone, add your test keys, run.
</Card>

## Quick start [#quick-start]

The recommended pattern is `loadXPay` at module level (or in a small loader file), then create a Checkout Session on your server, hold the returned `clientSecret` in state, and render `<XPayProvider>` once you have it. `useCheckout()` then reads the live session inside any child component.

```tsx title="lib/xpay.ts"
"use client";

import { loadXPay } from "@xpayeg/sdk";

// Module-level: load once, share across all components.
export const xpayPromise = loadXPay(process.env.NEXT_PUBLIC_XPAY_PUBLISHABLE_KEY!);
```

```tsx title="app/checkout/page.tsx"
"use client";

import { useState } from "react";
import { xpayPromise } from "@/lib/xpay";
import { PaymentElement, useCheckout, XPayProvider } from "@xpayeg/react";

export default function CheckoutPage() {
  const [clientSecret, setClientSecret] = useState<string | null>(null);
  const [creating, setCreating] = useState(false);

  const startCheckout = async () => {
    setCreating(true);
    const res = await fetch("/api/create-checkout", { method: "POST" });
    const { clientSecret } = await res.json();
    setClientSecret(clientSecret);
    setCreating(false);
  };

  if (!clientSecret) {
    return (
      <button onClick={startCheckout} disabled={creating}>
        {creating ? "Starting…" : "Pay now"}
      </button>
    );
  }

  return (
    <XPayProvider xpay={xpayPromise} options={{ clientSecret }}>
      <CheckoutForm />
    </XPayProvider>
  );
}

function CheckoutForm() {
  const state = useCheckout();

  if (state.type === "loading") return <p>Loading…</p>;
  if (state.type === "error") return <p>{state.error.message}</p>;

  const { checkout } = state;
  if (checkout.status.type === "expired") return <p>This checkout has expired.</p>;
  if (checkout.status.type === "complete") return <p>Payment complete. Thank you.</p>;

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await checkout.confirm({
      customerDetails: { email: "customer@example.com" },
    });
    if (result.type === "error") {
      alert(result.error.message);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button type="submit" disabled={!checkout.canConfirm}>
        Pay {checkout.currency} {(checkout.amountTotal / 100).toFixed(2)}
      </button>
    </form>
  );
}
```

The `clientSecret` lives in state, so `XPayProvider` only mounts once and `/api/create-checkout` is called exactly once per checkout. If you create the session earlier in your flow (a click on a product page, a server component, a route param), pass the resulting string in any way you like: props, search params, context. The provider only needs the string.

`useCheckout()` updates reactively after every promo code, quantity change, or fee recalculation. There's no need for an `onChange` callback in React; the component re-renders with the latest `checkout` data on its own.

## Exports at a glance [#exports-at-a-glance]

| Export                                       | Kind      | What it is                                                                              |
| -------------------------------------------- | --------- | --------------------------------------------------------------------------------------- |
| `XPayProvider`                               | component | Wraps your checkout UI and provides context to all child components.                    |
| `useCheckout`                                | hook      | Returns the disjoint union state and the merged session-plus-actions object.            |
| `useXPay`                                    | hook      | Returns the underlying `XPayInstance` (or `null`).                                      |
| `useElements`                                | hook      | Returns the underlying `Elements` instance (or `null`).                                 |
| `useConfirmPayment`                          | hook      | Convenience hook that exposes `confirmPayment` from the active checkout.                |
| `PaymentElement`                             | component | Mounts the payment-method selector with the card form.                                  |
| `CheckoutButton`                             | component | A pre-built button that opens the drop-in checkout modal on click.                      |
| `Checkout`                                   | type      | `CheckoutSession & CheckoutActions`. The merged shape `useCheckout` returns on success. |
| `UseCheckoutResult`                          | type      | The disjoint union returned by `useCheckout`.                                           |
| `PaymentElementProps`, `CheckoutButtonProps` | type      | Component prop types.                                                                   |

The package re-exports a few core types from `@xpayeg/sdk` for convenience: `CheckoutSession`, `CheckoutActions`, `PaymentMethodInfo`, `Appearance`, `ActionResult`, `XPayError`, `ConfirmPaymentOptions`, `PaymentElementChangeEvent`, `ElementsOptions`. Import additional types directly from `@xpayeg/sdk` when you need them.

## `<XPayProvider>` [#xpayprovider]

Wraps the part of your app that runs the checkout. Creates an `Elements` instance from `options.clientSecret` and provides three React contexts: the `XPayInstance`, the `Elements`, and the loading-state-aware checkout state.

```tsx
<XPayProvider xpay={xpayPromise} options={{ clientSecret, appearance, locale }}>
  {children}
</XPayProvider>
```

| Prop                   | Type                                                    | Description                                                                                       |
| ---------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `xpay`                 | `XPayInstance \| Promise<XPayInstance \| null> \| null` | The instance from `loadXPay()`. Pass the Promise; the provider awaits it on the client.           |
| `options.clientSecret` | `string \| Promise<string>`                             | The Checkout Session client secret. Required to create an Elements instance and load the session. |
| `options.appearance`   | `Appearance`                                            | Optional. Branding overrides merged with the session's server-side branding.                      |
| `options.locale`       | `"en" \| "ar"`                                          | Optional. Defaults to `"en"`.                                                                     |
| `children`             | `ReactNode`                                             | The subtree that calls `useCheckout`, `useXPay`, `useElements`, `useConfirmPayment`.              |

The provider handles SSR gracefully: `loadXPay` returns `null` on the server, and the provider waits for the client to load the SDK before creating the Elements instance.

## `useCheckout()` [#usecheckout]

The primary hook. Returns a disjoint union; narrow by `state.type` before reading session data or calling actions.

```ts
type UseCheckoutResult =
  | { type: "loading" }
  | { type: "error"; error: { message: string } }
  | { type: "success"; checkout: Checkout };

type Checkout = CheckoutSession & CheckoutActions;
```

```tsx
const state = useCheckout();
if (state.type === "loading") return <Spinner />;
if (state.type === "error") return <p>{state.error.message}</p>;
const { checkout } = state;
```

After narrowing to `success`, `checkout` carries every session field plus every action method.

### Session fields [#session-fields]

The session-data fields on `checkout` come straight from `CheckoutSession`. See the [`@xpayeg/sdk` reference](/sdk/sdk-js#checkoutsession-data-type) for the full schema. Most-used fields:

| Field            | Type                   | Description                                                             |
| ---------------- | ---------------------- | ----------------------------------------------------------------------- |
| `id`             | `string`               | Session ID.                                                             |
| `amountTotal`    | `number`               | Total in minor units.                                                   |
| `amountSubtotal` | `number`               | Subtotal in minor units.                                                |
| `currency`       | `string`               | Currency code.                                                          |
| `merchantName`   | `string`               | Your business name.                                                     |
| `livemode`       | `boolean`              | Live vs test mode.                                                      |
| `status`         | `SessionStatus`        | `{type:"open"} \| {type:"expired"} \| {type:"complete", paymentStatus}` |
| `canConfirm`     | `boolean`              | Whether the session is ready to confirm.                                |
| `paymentMethods` | `PaymentMethodInfo[]`  | Available methods on this session.                                      |
| `lineItems`      | `CheckoutLineItem[]`   | Line items.                                                             |
| `totalDetails`   | `CheckoutTotalDetails` | Subtotal, tax, shipping, discount, fee breakdown.                       |
| `discounts`      | `CheckoutDiscount[]`   | Applied promotion codes.                                                |

### Action methods [#action-methods]

Action methods come from `CheckoutActions`. All return a `Promise<ActionResult>` (a tagged union of `success` or `error`) except where noted.

| Method                                                       | Description                                                                 |
| ------------------------------------------------------------ | --------------------------------------------------------------------------- |
| `confirm(options?: Omit<ConfirmPaymentOptions, "elements">)` | Confirms the payment. Handles 3DS challenges and BNPL redirects internally. |
| `applyPromotionCode(code: string)`                           | Applies a promo code; the session re-renders with the new total.            |
| `removePromotionCode()`                                      | Removes the applied code.                                                   |
| `updateLineItemQuantity({ lineItem, quantity })`             | Updates a line item's quantity.                                             |
| `submit()`                                                   | Validates fields. Returns `{ error?, selectedPaymentMethod? }`.             |
| `fetchUpdates()`                                             | Re-fetches the session from the server.                                     |
| `changeAppearance(appearance: Appearance)`                   | Updates branding at runtime; returns `void`.                                |
| `on(event, handler)`                                         | Listens for `"change"`, `"error"`, or a custom event. Returns `void`.       |
| `getElements()`                                              | Returns the underlying `Elements` instance for low-level access.            |

The hook listens for the underlying `change` and `loaderror` events itself, so when promo codes are applied, line items update, or fees recalculate, the component re-renders with the latest data without you wiring an effect.

### Promo codes and quantities (example) [#promo-codes-and-quantities-example]

```tsx
function LineItems() {
  const state = useCheckout();
  if (state.type !== "success") return null;
  const { checkout } = state;

  return (
    <ul>
      {checkout.lineItems?.map((item) => (
        <li key={item.id}>
          <span>{item.description}</span>
          <button
            onClick={() =>
              checkout.updateLineItemQuantity({
                lineItem: item.id,
                quantity: item.quantity + 1,
              })
            }
          >
            +
          </button>
          <button
            onClick={() =>
              checkout.updateLineItemQuantity({
                lineItem: item.id,
                quantity: Math.max(1, item.quantity - 1),
              })
            }
          >
            -
          </button>
        </li>
      ))}
    </ul>
  );
}

function PromoInput() {
  const state = useCheckout();
  const [code, setCode] = useState("");
  const [error, setError] = useState("");

  if (state.type !== "success") return null;
  const { checkout } = state;

  const apply = async () => {
    setError("");
    const result = await checkout.applyPromotionCode(code);
    if (result.type === "error") setError(result.error.message);
  };

  return (
    <div>
      <input value={code} onChange={(e) => setCode(e.target.value)} />
      <button onClick={apply}>Apply</button>
      <button onClick={() => checkout.removePromotionCode()}>Remove</button>
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}
```

### Listening for unsolicited errors [#listening-for-unsolicited-errors]

`change` events drive React state automatically. The `error` event covers errors that fire outside any merchant action (e.g. session expired during a fee recalculation, BIN detection failure):

```tsx
useEffect(() => {
  if (state.type !== "success") return;
  state.checkout.on("error", (err) => {
    console.error("[XPay] unsolicited error:", err.code, err.message);
  });
}, [state]);
```

## `useXPay()` and `useElements()` [#usexpay-and-useelements]

Lower-level hooks that return the underlying instances. Useful when you need to call methods that aren't exposed through `useCheckout` (for example, mounting an extra element programmatically).

```ts
function useXPay(): XPayInstance | null;
function useElements(): Elements | null;
```

Both return `null` while the SDK is loading or before a `clientSecret` has been provided. Both must be called inside `<XPayProvider>`.

## `useConfirmPayment()` [#useconfirmpayment]

Convenience hook around `useCheckout`. Returns the same `confirm` function plus an `isConfirming` flag. Useful when you want a tiny API for a leaf component that only needs to submit:

```tsx
const { confirmPayment, isConfirming } = useConfirmPayment();
```

If the checkout isn't ready yet, `confirmPayment()` returns `{ type: "error", error: { message: "Checkout not ready" } }` immediately rather than throwing.

## `<PaymentElement />` [#paymentelement-]

Renders the payment-method selector and card form. Must be inside `<XPayProvider>` with a session.

```tsx
<PaymentElement
  onChange={(e) => setReady(e.complete)}
  onReady={() => console.log("element ready")}
/>
```

| Prop            | Type                                         | Description                                                    |
| --------------- | -------------------------------------------- | -------------------------------------------------------------- |
| `onReady`       | `() => void`                                 | Optional. Fires when the element's iframe is initialized.      |
| `onChange`      | `(event: PaymentElementChangeEvent) => void` | Optional. Fires on payment-method selection and field changes. |
| `onLoaderStart` | `() => void`                                 | Optional. Fires synchronously when the iframe is created.      |
| `onLoadError`   | `(event: ElementsLoadErrorEvent) => void`    | Optional. Fires when the element fails to load.                |
| `className`     | `string`                                     | Optional. CSS class for the container `<div>`.                 |
| `id`            | `string`                                     | Optional. ID for the container `<div>`.                        |

The component renders a single `<div>` and mounts the underlying `PaymentElement` (from `@xpayeg/sdk`) into it. On unmount, the element is torn down automatically.

## `<CheckoutButton />` [#checkoutbutton-]

Pre-built button that opens the drop-in checkout modal on click. Wraps `xpay.checkout({ mode: "modal" })` so you don't have to wire it yourself.

```tsx
<CheckoutButton
  clientSecret="cs_test_..."
  checkoutOptions={{
    onComplete: (result) => router.push(`/orders/${result.paymentIntentId}`),
    onClose: () => console.log("Customer closed checkout"),
  }}
>
  Pay Now
</CheckoutButton>
```

| Prop              | Type                                              | Description                                                                                                 |
| ----------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `clientSecret`    | `string`                                          | Required. The Checkout Session client secret.                                                               |
| `children`        | `ReactNode`                                       | Button label. Defaults to `"Pay"`.                                                                          |
| `checkoutOptions` | `Omit<CheckoutOptions, "clientSecret" \| "mode">` | Optional. Callbacks (`onComplete`, `onClose`, `onReady`, `onConfirmed`, `onError`), `appearance`, `locale`. |
| `className`       | `string`                                          | Optional. CSS class for the button.                                                                         |
| `disabled`        | `boolean`                                         | Optional. Disables the button.                                                                              |

The button is also disabled automatically when the SDK isn't loaded yet (`useXPay()` returns `null`). Must be inside `<XPayProvider>`.

## Common patterns [#common-patterns]

### Theme-aware appearance [#theme-aware-appearance]

Sync XPay's color mode with your app's theme using `changeAppearance` at runtime:

```tsx
function ThemeAwareCheckout() {
  const state = useCheckout();
  const { theme } = useTheme();

  useEffect(() => {
    if (state.type === "success") {
      state.checkout.changeAppearance({ colorMode: theme as "light" | "dark" });
    }
  }, [theme, state]);

  return <PaymentElement />;
}
```

### Pre-validating before confirming [#pre-validating-before-confirming]

`submit()` validates all fields before `confirm()`. Useful when you want to gate the confirm step behind another UI step (a confirmation dialog, a Terms checkbox, etc.):

```tsx
const handlePay = async () => {
  const { error, selectedPaymentMethod } = await checkout.submit();
  if (error) return setError(error.message);

  const ok = await showConfirmDialog(selectedPaymentMethod);
  if (!ok) return;

  const result = await checkout.confirm();
  if (result.type === "error") setError(result.error.message);
};
```

### Redirect after success [#redirect-after-success]

By default `confirm()` returns the result to your code. Pass `redirect: "always"` to send the customer to your `afterCompletion.redirect.url` after success:

```tsx
await checkout.confirm({
  customerDetails: { email },
  redirect: "always",
});
// On success, the page navigates away. Any code below only runs on error.
```

Set the destination on the session as `afterCompletion.redirect.url`. XPay navigates to it unchanged, appending nothing. Put `{CHECKOUT_SESSION_ID}` in the URL if your return page needs to know which session it is.

## Where to next [#where-to-next]

<Cards>
  <Card icon="<Code />" title="@xpayeg/sdk" href="/sdk/sdk-js">
    The underlying JavaScript SDK. Reference for `loadXPay`, `XPayInstance`, `Elements`, drop-in checkout, and every type.
  </Card>

  <Card icon="<Sparkles />" title="Drop-in pattern" href="/integrate/integration-patterns/drop-in">
    Walkthrough for the modal/inline checkout via `<CheckoutButton />` or `xpay.checkout()`.
  </Card>

  <Card icon="<Sparkles />" title="Elements pattern" href="/integrate/integration-patterns/elements">
    Walkthrough for `<XPayProvider>` + `<PaymentElement />` + `useCheckout`.
  </Card>
</Cards>