Skip to main content

Getting Started

Learn how to integrate Odus Checkout SDK into your application. This guide walks you through the complete setup process with code examples in React.

Prerequisites

Before you begin, make sure you have:

  1. A publishable key from your Odus Dashboard — this is required to initialize the checkout on the frontend and is safe to expose publicly.
  2. A backend endpoint that creates payments via the Odus API — see Making Payments for details.

How It Works

Odus Checkout uses a two-step initialization process:

  1. Mount — Renders the checkout form UI (card inputs, pay button) into your page
  2. Associate — Links the checkout to a payment created on your backend, enabling the pay button and loading available payment methods

The checkout is interactive immediately after mounting, but the pay button remains disabled until a payment is associated. You should both mount and associate as early as possible in your checkout flow to minimize wait times for customers.

Step 1: Install and Mount the Checkout

First, install the Checkout SDK via NPM:

npm install @odus/checkout

Then we'll create a React component that mounts the checkout form. At this stage, we'll set up the basic structure — the checkout UI will render, but the pay button won't be functional yet.

src/components/CheckoutForm.tsx
import { useEffect, useRef } from 'react';
import { OdusCheckout } from '@odus/checkout';
import '@odus/checkout/styles'; // Required for proper styling

export function CheckoutForm() {
const checkoutRef = useRef<OdusCheckout | null>(null);

useEffect(() => {
const checkout = new OdusCheckout({
environment: 'test', // Use 'live' for production
apiKey: 'pk_test_your_publishable_key',
returnUrl: 'https://your-website.com/checkout/return',
});

checkout.mount('#checkout-container');
checkoutRef.current = checkout;

return () => {
checkoutRef.current?.unmount();
};
}, []);

return <div id="checkout-container" />;
}

After this step, you'll see the card input form rendered on the page. The pay button appears but is disabled — that's expected. Let's fix that in the next step.

important

Don't forget to import the styles with import '@odus/checkout/styles'. Without this, the checkout form won't render correctly.

Step 2: Associate a Payment

For the checkout to accept payments, you need to link it to a payment created on your backend. Your backend should call the Odus API to create a payment and return two values: paymentId and checkoutKey.

info

Learn how to create payments on your backend in the Making Payments guide.

Here's the complete component with payment association and proper error handling:

src/components/CheckoutForm.tsx
import { useEffect, useRef, useState } from 'react';
import { OdusCheckout } from '@odus/checkout';
import '@odus/checkout/styles';

export function CheckoutForm() {
const checkoutRef = useRef<OdusCheckout | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const checkout = new OdusCheckout({
environment: 'test',
apiKey: 'pk_test_your_publishable_key',
returnUrl: 'https://your-website.com/checkout/return',
callbacks: {
onPaymentSucceeded: () => {
window.location.href = '/checkout/success';
},
onPaymentFailed: (errorMessage) => {
setError(`Payment failed: ${errorMessage}`);
},
},
});

checkout.mount('#checkout-container');
checkoutRef.current = checkout;

// Fetch payment details from your backend and associate
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?.unmount();
};
}, []);

return (
<div>
{isLoading && <div className="loading-spinner">Loading checkout...</div>}
{error && <div className="error-message">{error}</div>}
<div id="checkout-container" />
</div>
);
}

Once associatePayment() completes successfully:

  • The pay button becomes active
  • Alternative payment methods (PayPal, Apple Pay) appear if configured
  • Customers can complete their purchase
tip

Call associatePayment() as early as possible in your checkout flow. This minimizes the time customers spend waiting for the checkout to become ready.

Updating the Payment

In many checkout flows, the customer can change their selected plan or pricing option after the checkout form is already rendered. When this happens, you need to create a new payment on your backend and re-associate it with the existing checkout instance.

Call associatePayment() again on the same instance — do not unmount and recreate the checkout. The SDK preserves all user-entered form data (email, card details, address fields) and only refreshes the payment method buttons.

src/components/CheckoutForm.tsx
import { useEffect, useRef, useState, useCallback } from 'react';
import { OdusCheckout } from '@odus/checkout';
import '@odus/checkout/styles';

const PRICES = [
{ id: 'basic', label: 'Basic — $9/mo', amount: 900 },
{ id: 'pro', label: 'Pro — $29/mo', amount: 2900 },
];

export function CheckoutForm() {
const checkoutRef = useRef<OdusCheckout | null>(null);
const [selectedPrice, setSelectedPrice] = useState(PRICES[0]);

useEffect(() => {
const checkout = new OdusCheckout({
environment: 'test',
apiKey: 'pk_test_your_publishable_key',
returnUrl: 'https://your-website.com/checkout/return',
callbacks: {
onPaymentSucceeded: () => {
window.location.href = '/checkout/success';
},
},
});

checkout.mount('#checkout-container');
checkoutRef.current = checkout;

return () => {
checkoutRef.current?.unmount();
};
}, []);

// Re-associate whenever the selected 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>
<div>
{PRICES.map((price) => (
<button key={price.id} onClick={() => setSelectedPrice(price)}>
{price.label}
</button>
))}
</div>
<div id="checkout-container" />
</div>
);
}
warning

Do not destroy and recreate the checkout instance when the price changes. Unmounting and remounting creates a new form, which wipes all fields the customer has already filled in.

Next Steps

Now that you have the basics set up, there are only a few steps left to get your checkout flow production-ready:

  • 3DS and Redirect Handling — Handle redirect-based payment flows like 3DS authentication and PayPal
  • Configuring Checkout — Customize the appearance and behavior of the checkout form
  • Builder — Visually experiment with styles, layout, and payment methods in an interactive playground