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

# Payment Elements (SDK)

Payment Elements is a React SDK that renders the payment-method form for one
[payment intent](/api-reference/schema-payment-intent) and confirms it from the
browser. Your server creates the intent and hands the `clientSecret` to the
page; the SDK does the rest. You keep full control of the surrounding checkout
UI, including the email, name, and billing-address fields. Requires React 18
or 19.

```bash theme={null}
npm install @slashfi/elements-react
```

## Overview

1. Your server calls
   [`POST /payments/payment-intent`](/api-reference/payment-intent-post) and
   returns the `clientSecret` to the browser. The secret is scoped to that one
   intent and is only issued on the create response; the SDK uses it both to
   load the intent and to confirm the payment on the buyer's behalf.
2. Wrap your checkout in `<PaymentProvider clientSecret={...}>`, render
   `<PaymentElement />` where the payment-method form should appear, and
   render the required `<BrandingElement />`.
3. Collect email, name, and billing address in your own fields, then call
   `confirm()` from `usePaymentElements()`.
4. Treat the result as "the buyer is done", not "the payment settled". Fulfill
   from your server when the intent's `status` becomes `succeeded`, via the
   [`payments.payment_intent.updated`](/docs/payments/webhooks/payment-intent-updated)
   webhook.

## Components

### `<PaymentProvider>`

Loads the payment intent for the given `clientSecret` and provides state to
everything inside it. Render one provider per payment intent.

| Prop           | Type               | Description                                                                                                                               |
| -------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `clientSecret` | `string`           | **Required.** From the `POST /payments/payment-intent` response. Changing it remounts the provider's children with fresh, isolated state. |
| `locale`       | `string \| 'auto'` | Optional BCP 47 locale for the payment form (for example `"en"`, `"fr"`). Unsupported values and `'auto'` fall back to English.           |
| `children`     | `ReactNode`        | Your checkout UI.                                                                                                                         |

The provider is safe to render on the server: it emits non-interactive
placeholders until it runs in the browser.

### `<PaymentElement>`

The payment-method form. Renders the card input and any wallets enabled for
your account. Billing details are **not** collected here; you own those fields
and pass them to `confirm()`. The element reports its completeness to the
provider, which drives `canConfirm`.

### `<BrandingElement>`

Renders the required payment-processing attribution.

<Warning>
  **`<BrandingElement />` is required.** It must be rendered inside the
  `<PaymentProvider>` on every page that renders `<PaymentElement />`.
  Payments from pages that omit it will be blocked. Place it near your pay
  button.
</Warning>

## React hooks

### `usePaymentElements()`

Returns the current `PaymentInitializationState`. Must be called inside a
`<PaymentProvider>`. The state is a discriminated union on `state`:

| `state`     | Fields                                      | Meaning                                                                                 |
| ----------- | ------------------------------------------- | --------------------------------------------------------------------------------------- |
| `'loading'` | —                                           | The payment intent is being loaded. Show a spinner and keep the pay button disabled.    |
| `'error'`   | `error: PaymentInitializationError`         | The form could not be initialized. See [Initialization errors](#initialization-errors). |
| `'ready'`   | `canConfirm`, `confirm()`, `fetchUpdates()` | The form is mounted and interactive.                                                    |

In the `'ready'` state:

* **`canConfirm: boolean`** — `true` once the buyer has filled in a complete
  payment method. Bind your pay button's `disabled` to `!canConfirm`.
* **`confirm(input): Promise<PaymentResult>`** — see
  [Static methods](#static-methods).
* **`fetchUpdates(): Promise<void>`** — see
  [Static methods](#static-methods).

## Static methods

Available on the `'ready'` state returned by `usePaymentElements()`.

### `confirm()`

Tokenizes the payment method, confirms the intent, and runs any buyer step
(such as 3-D Secure) inline. Resolves to a [`PaymentResult`](#paymentresult)
once the buyer is done or the attempt failed.

Takes a single object with the buyer details you collected. Missing or
malformed fields are rejected before any network call with a
`validation_error` listing each offending field.

<ResponseField name="email" type="string" required>
  Buyer's email.
</ResponseField>

<ResponseField name="name" type="string" required>
  Buyer's name as it appears on the card.
</ResponseField>

<ResponseField name="billingAddress" type="object" required>
  Billing address for the payment method.

  <Expandable title="properties">
    <ResponseField name="line1" type="string" required>
      Street address.
    </ResponseField>

    <ResponseField name="line2" type="string">
      Apartment, suite, or unit.
    </ResponseField>

    <ResponseField name="city" type="string">
      City.
    </ResponseField>

    <ResponseField name="state" type="string">
      State, province, or region.
    </ResponseField>

    <ResponseField name="postalCode" type="string" required>
      Postal or ZIP code.
    </ResponseField>

    <ResponseField name="country" type="string" required>
      ISO 3166-1 alpha-2 country code, for example `"US"`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="returnUrl" type="string">
  `https://` URL the buyer is sent back to if a payment method requires a
  full-page redirect. Provide it if you enable wallets or redirect-based
  methods.
</ResponseField>

### `fetchUpdates()`

Reloads the intent's current amount, currency, and allowed payment methods.
Call it after your server changes the intent (for example after applying a
discount) so the form reflects the new amount, and after a
`payment_intent_updated` error before asking the buyer to confirm again.
Overlapping calls share one request.

## Results and errors

### `PaymentResult`

`confirm()` never throws for payment outcomes; it resolves to exactly one of:

| `type`         | Fields                    | Meaning                                                                                                                                                                                                                       |
| -------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'processing'` | `paymentIntentId: string` | The buyer's step is complete (including any redirect handoff). **This is not a settlement guarantee.** Leave the checkout, and let your server fulfill on `status: "succeeded"`. Do not start another attempt on this intent. |
| `'error'`      | `error: PaymentError`     | Confirmation did not complete. Show `error.message` and let the buyer try again where the error allows it (see below).                                                                                                        |

Repeated `confirm()` calls for the same intent while one is in flight share the
same pending promise.

### `PaymentError`

| `type`                  | `code`                     | Retryable?                  | Meaning                                                                                                                                                                                                                            |
| ----------------------- | -------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validation_error`      | `invalid_customer_details` | Yes                         | One or more `confirm()` input fields are missing or invalid. `issues[]` lists `{ field, code: 'missing' \| 'invalid', message }` per field, with `field` values such as `email`, `name`, `billingAddress.postalCode`, `returnUrl`. |
| `payment_error`         | `payment_method_declined`  | Yes                         | The card was declined. `declineCode` may carry the network reason. The intent returns to `pending`; the buyer can try another card.                                                                                                |
| `payment_error`         | `payment_failed`           | Yes                         | The payment attempt failed for a non-decline reason.                                                                                                                                                                               |
| `invalid_request_error` | `invalid_client_secret`    | No                          | The `clientSecret` is wrong or no longer valid. Create a new payment intent.                                                                                                                                                       |
| `invalid_request_error` | `payment_intent_updated`   | Yes, after `fetchUpdates()` | The intent changed on the server since the form loaded (for example the amount). Call `fetchUpdates()` and ask the buyer to confirm again.                                                                                         |
| `invalid_request_error` | `terminal_payment_intent`  | No                          | The intent is already `succeeded` or `canceled`.                                                                                                                                                                                   |
| `api_error`             | `unknown`                  | Check server first          | A network or provider error. The attempt may still be locked server-side, so read the intent from your server before retrying.                                                                                                     |

### Initialization errors

When `usePaymentElements()` returns `state: 'error'`, `error` is a
`PaymentInitializationError`:

| `type`                  | `code`                    | Meaning                                                             |
| ----------------------- | ------------------------- | ------------------------------------------------------------------- |
| `invalid_request_error` | `invalid_client_secret`   | The `clientSecret` is wrong or expired.                             |
| `invalid_request_error` | `terminal_payment_intent` | The intent is already `succeeded` or `canceled` and cannot be paid. |
| `api_error`             | `load_failed`             | The payment form assets could not be loaded.                        |
| `api_error`             | `unknown`                 | Any other failure while loading the intent.                         |

## Full example

```tsx theme={null}
import { useState } from 'react';
import {
  BrandingElement,
  PaymentElement,
  PaymentProvider,
  usePaymentElements,
  type PaymentError,
} from '@slashfi/elements-react';

function CheckoutForm() {
  const payment = usePaymentElements();
  const [email, setEmail] = useState('');
  const [name, setName] = useState('');
  const [postalCode, setPostalCode] = useState('');
  const [error, setError] = useState<PaymentError | null>(null);
  const [submitting, setSubmitting] = useState(false);

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

  async function handlePay() {
    setSubmitting(true);
    setError(null);

    const result = await payment.confirm({
      email,
      name,
      billingAddress: { line1: '123 Main St', postalCode, country: 'US' },
      returnUrl: 'https://merchant.example/checkout/complete',
    });

    setSubmitting(false);

    if (result.type === 'error') {
      setError(result.error);
      return;
    }

    // The buyer is done. Your server fulfills when the intent is `succeeded`.
    window.location.assign(`/orders/pending?pi=${result.paymentIntentId}`);
  }

  return (
    <>
      <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name on card" />
      <input value={postalCode} onChange={(e) => setPostalCode(e.target.value)} placeholder="ZIP" />

      <PaymentElement />

      {error && <p role="alert">{error.message}</p>}

      <button
        type="button"
        disabled={!payment.canConfirm || submitting}
        onClick={() => void handlePay()}
      >
        Pay
      </button>

      {/* Required. Payments are blocked if this is not rendered. */}
      <BrandingElement />
    </>
  );
}

export function Checkout({ clientSecret }: { clientSecret: string }) {
  return (
    <PaymentProvider clientSecret={clientSecret}>
      <CheckoutForm />
    </PaymentProvider>
  );
}
```

## Gotchas

* **`processing` is not success.** Confirmation resolving means the buyer's
  part is over. Settlement happens asynchronously; the only signal to fulfill
  on is the intent's `status` reaching `succeeded`, read from your server.
  Never grant goods or services based on a browser-side result alone.
* **Declines are retryable in place.** A `payment_method_declined` result
  returns the intent to `pending`, so the buyer can enter another card and
  `confirm()` again without a new intent.
* **After an `api_error`, check before retrying.** The attempt may still hold
  the intent's processing lock. Read the intent from your server; if it is
  `pending`, the buyer can retry.
* **Redirect-based methods.** If the payment method sends the buyer to another
  page, `confirm()` resolves `processing` at the moment of handoff and the
  buyer returns to `returnUrl`. Treat that landing page like any other
  post-checkout page: wait for the webhook.
* **One intent per provider.** To switch intents, render a new
  `<PaymentProvider>` with the new `clientSecret`; state does not carry over.
