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:
- A publishable key from your Odus Dashboard — this is required to initialize the checkout on the frontend and is safe to expose publicly.
- 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:
- Mount — Renders the checkout form UI (card inputs, pay button) into your page
- 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.
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.
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.
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:
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
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.
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>
);
}
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