# Getting Started

This guide walks you through integrating ODUS Elements into a React application — from installation to accepting your first payment.

## Prerequisites

Before you begin, make sure you have:

1. A backend endpoint that creates payments via the ODUS API — see [Making Payments](https://docs.odus.com/docs/guides/payments/making-payments.md).

## How It Works

ODUS Elements follows a three-step flow:

1. **Create** — Initialize `OdusElements` with your API key, environment, and callbacks.
2. **Mount** — Place individual UI elements (card input, email, error display, etc.) into your page using refs or CSS selectors.
3. **Associate & Authorize** — Link a server-side payment with `associatePayment()`, then call `authorize()` from your submit button.

You choose which elements to mount. Only mounted elements participate in validation and authorization — unmounted elements are automatically skipped. This lets you build anything from a minimal card-only form to a full checkout with address fields and express payment buttons.

## Step 1: Install the SDK

```
npm install @odus/checkout@latest
```

If your app already depends on the SDK, make sure it resolves to version `1.12.0` or later before 4 November 2026. Version `1.12.0` is the first SDK release that calls only API v2. SDK versions that call API v1 routes stop working when v1 stops being served.

## Step 2: Create and Mount Elements

Initialize `OdusElements` and mount elements into container refs:

```
import { OdusElements } from '@odus/checkout/elements';



const checkout = new OdusElements({

  environment: 'test',

  apiKey: 'pk_retired',

  returnUrl: 'https://your-website.com/checkout/return',

  callbacks: {

    onPaymentSucceeded: () => {

      window.location.href = '/checkout/success';

    },

    onPaymentFailed: (error) => {

      console.error('Payment failed:', error);

    },

    onActionRequired: (url) => {

      window.location.href = url;

    },

  },

});



// Mount elements into DOM containers

checkout.elements.contact.email.mount(emailRef.current!);

checkout.elements.card.mount(cardRef.current!);

checkout.elements.error.mount(errorRef.current!);
```

At this point the inputs render on the page, but no payment is linked yet.

info

Elements ship with built-in styles — no separate CSS import is needed. Each element injects its own scoped styles via Shadow DOM.

## Step 3: Associate a Payment and Authorize

Your backend creates a payment via the ODUS API and returns a `paymentId` and `checkoutKey`. Call `associatePayment()` to link them, then `authorize()` when the customer submits.

info

Learn how to create payments on your backend in the [Making Payments](https://docs.odus.com/docs/guides/payments/making-payments.md) guide.

```
// Link the server-side payment

const { paymentId, checkoutKey } = await fetch('/api/create-payment', {

  method: 'POST',

}).then((res) => res.json());



await checkout.associatePayment(paymentId, checkoutKey);



// On submit: validate, then authorize

const { valid, errors } = checkout.validate();

if (valid) {

  await checkout.authorize();

}
```

## Complete Example

Here's a full React component tying all three steps together:

src/components/CheckoutForm.tsx

```
import { useEffect, useRef, useState } from 'react';

import { OdusElements } from '@odus/checkout/elements';



export function CheckoutForm() {

  const checkoutRef = useRef<OdusElements | null>(null);

  const emailRef = useRef<HTMLDivElement>(null);

  const cardRef = useRef<HTMLDivElement>(null);

  const errorRef = useRef<HTMLDivElement>(null);



  const [isLoading, setIsLoading] = useState(true);

  const [error, setError] = useState<string | null>(null);

  const [isFormValid, setIsFormValid] = useState(false);



  useEffect(() => {

    const checkout = new OdusElements({

      environment: 'test',

      apiKey: 'pk_retired',

      returnUrl: 'https://your-website.com/checkout/return',

      callbacks: {

        onPaymentSucceeded: () => {

          window.location.href = '/checkout/success';

        },

        onPaymentFailed: (errorMessage) => {

          setError(`Payment failed: ${errorMessage}`);

        },

        onActionRequired: (redirectUrl) => {

          window.location.href = redirectUrl;

        },

      },

    });



    checkout.elements.contact.email.mount(emailRef.current!);

    checkout.elements.card.mount(cardRef.current!);

    checkout.elements.error.mount(errorRef.current!);



    checkout.onChange(() => {

      setIsFormValid(checkout.isValid);

    });



    checkoutRef.current = checkout;



    async function setupPayment() {

      try {

        const response = await fetch('/api/create-payment', {

          method: 'POST',

          headers: { 'Content-Type': 'application/json' },

        });



        if (!response.ok) throw new Error('Failed to create payment');



        const { paymentId, checkoutKey } = await response.json();

        await checkout.associatePayment(paymentId, checkoutKey);

      } catch (err) {

        setError('Failed to initialize payment. Please refresh the page.');

      } finally {

        setIsLoading(false);

      }

    }



    setupPayment();



    return () => {

      checkoutRef.current?.destroy();

    };

  }, []);



  async function handlePay() {

    const checkout = checkoutRef.current;

    if (!checkout) return;



    const { valid, errors } = checkout.validate();

    if (!valid) {

      console.log('Validation errors:', errors);

      return;

    }



    await checkout.authorize();

  }



  return (

    <div>

      {isLoading && <p>Loading checkout...</p>}

      {error && <p style={{ color: 'red' }}>{error}</p>}



      <div ref={emailRef} />

      <div ref={cardRef} />

      <div ref={errorRef} />



      <button onClick={handlePay} disabled={isLoading || !isFormValid}>

        Pay

      </button>

    </div>

  );

}
```

tip

Call `associatePayment()` as early as possible. This minimizes customer wait time and ensures express payment methods (Apple Pay, PayPal) render promptly.

## Updating the Payment

When the customer changes their selected plan or pricing option after the form is already rendered, create a new payment on your backend and call `associatePayment()` again on the **same instance**. Do not destroy and recreate the Elements instance — the SDK preserves all user-entered form data and only refreshes the express payment buttons.

src/components/CheckoutForm.tsx

```
const PRICES = [

  { id: 'basic', label: 'Basic — $9/mo', amount: 900 },

  { id: 'pro', label: 'Pro — $29/mo', amount: 2900 },

];



export function CheckoutForm() {

  const checkoutRef = useRef<OdusElements | null>(null);

  const [selectedPrice, setSelectedPrice] = useState(PRICES[0]);



  // Mount once — no dependency on selectedPrice

  useEffect(() => {

    const checkout = new OdusElements({

      /* config */

    });

    checkout.elements.contact.email.mount(emailRef.current!);

    checkout.elements.card.mount(cardRef.current!);

    checkoutRef.current = checkout;



    return () => {

      checkoutRef.current?.destroy();

    };

  }, []);



  // Re-associate when the price changes

  useEffect(() => {

    const checkout = checkoutRef.current;

    if (!checkout) return;



    async function updatePayment() {

      const response = await fetch('/api/create-payment', {

        method: 'POST',

        headers: { 'Content-Type': 'application/json' },

        body: JSON.stringify({ amount: selectedPrice.amount }),

      });

      const { paymentId, checkoutKey } = await response.json();

      await checkout!.associatePayment(paymentId, checkoutKey);

    }



    updatePayment();

  }, [selectedPrice]);



  return (

    <div>

      {PRICES.map((price) => (

        <button key={price.id} onClick={() => setSelectedPrice(price)}>

          {price.label}

        </button>

      ))}

      <div ref={emailRef} />

      <div ref={cardRef} />

      <button onClick={handlePay}>Pay</button>

    </div>

  );

}
```

warning

Do not destroy and recreate the Elements instance when the price changes. This creates new elements from scratch, which **wipes all fields** the customer has already filled in.

## Next Steps

* [Configuration](https://docs.odus.com/docs/guides/checkout/elements/configuration.md) — Customize callbacks, localization, and additional payment methods
* [Theming](https://docs.odus.com/docs/guides/checkout/elements/theming.md) — Style elements using CSS custom properties
* [Examples](https://docs.odus.com/docs/guides/checkout/elements/examples.md) — Integration patterns: minimal, PayPal-only, custom payload, and more
* [SDK Reference](https://docs.odus.com/docs/guides/checkout/elements/sdk-reference.md) — Full API reference for all classes and types
* [3DS and Redirect Handling](https://docs.odus.com/docs/guides/checkout/3ds-and-redirect-handling.md) — Handle redirect-based flows like 3DS and PayPal
* [Builder](https://docs.odus.com/docs/guides/checkout/elements/builder.md) — Drag and drop elements, adjust themes, and preview your layout in an interactive playground
