Skip to main content

useCheckoutButton

Overview

useCheckoutButton is a React hook that encapsulates all LemonSqueezy checkout button logic. It supports both redirect-based and embedded checkout modes, custom pricing, and provides handlers for form submission, button clicks, and price input changes.

Source: template/hooks/use-checkout-button.ts

Signature

function useCheckoutButton(params?: CheckoutButtonParams): UseCheckoutButtonReturn

Parameters

ParameterTypeRequiredDescription
paramsCheckoutButtonParamsNoOptional configuration for checkout behavior.

CheckoutButtonParams

PropertyTypeDefaultDescription
defaultEmailstring''Pre-filled email for the checkout. If empty, a form is shown.
defaultPricenumberundefinedInitial custom price value.
variantIdnumberundefinedLemonSqueezy product variant ID.
metadataRecord<string, any>{}Additional metadata to pass to the checkout session.
embeddedbooleanfalseWhether to use embedded checkout mode instead of redirect.
onPaymentSuccess(res?: any) => voidundefinedCallback fired when embedded payment completes successfully.
onClose() => voidundefinedCallback fired when the embedded checkout overlay is closed.
darkbooleanundefinedWhether to use dark theme for the checkout.

Return Values

The hook returns an object that merges CheckoutButtonState and CheckoutButtonActions:

State

PropertyTypeDescription
customPricenumber | undefinedThe current custom price value.
showFormbooleanWhether to show the email form. true when no defaultEmail is provided.
isLoadingbooleantrue while the checkout session is being created.
errorError | nullThe error from the most recent checkout attempt.
isErrorbooleantrue if the most recent checkout attempt failed.
isSuccessbooleantrue if the most recent checkout session was created successfully.
checkoutUrlstring | nullThe embedded checkout URL. Only set in embedded mode.
isEmbedReadybooleanWhether the embedded checkout is ready to display. Always true in redirect mode.

Actions

PropertyTypeDescription
setCustomPrice(price: number | undefined) => voidUpdate the custom price value.
handleSubmit(e: React.FormEvent) => Promise<void>Form submission handler. Calls e.preventDefault() and triggers checkout.
handleClick() => Promise<void>Simple button click handler that triggers checkout.
handlePriceChange(e: React.ChangeEvent<HTMLInputElement>) => voidInput change handler for price fields. Parses integer values and validates >= 0.
clearCheckout() => voidClear the embedded checkout state. Only functional in embedded mode.
handleSubmitWithParams(params?: CheckoutButtonParams) => Promise<void>Execute checkout with optional override parameters.

Implementation Details

  • Checkout Modes: The hook internally uses useLemonSqueezyCheckoutWithRedirect for redirect mode and useLemonSqueezyEmbeddedCheckout for embedded mode. The active hook is selected based on the embedded parameter.
  • Metadata Injection: A source: 'checkout-button' field and a timestamp are automatically added to checkout metadata.
  • Price Validation: The handlePriceChange handler only accepts non-negative integers. Empty input sets the price to undefined.
  • Error Handling: Checkout errors are caught, logged to the console, and re-thrown for upstream handling.

Dependencies

  • useLemonSqueezyCheckoutWithRedirect -- From @/hooks/use-lemonsqueezy-queries
  • useLemonSqueezyEmbeddedCheckout -- From @/hooks/use-lemonsqueezy-queries

Usage Examples

Redirect Checkout Button

import { useCheckoutButton } from '@/hooks/use-checkout-button';

function SimpleCheckoutButton({ variantId }: { variantId: number }) {
const { handleClick, isLoading } = useCheckoutButton({
variantId,
defaultEmail: 'user@example.com'
});

return (
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? 'Processing...' : 'Subscribe Now'}
</button>
);
}

Embedded Checkout with Custom Price

function EmbeddedCheckout({ variantId }: { variantId: number }) {
const {
customPrice,
handlePriceChange,
handleSubmit,
isLoading,
checkoutUrl,
isEmbedReady,
clearCheckout
} = useCheckoutButton({
variantId,
embedded: true,
defaultPrice: 1000,
onPaymentSuccess: (res) => {
console.log('Payment succeeded:', res);
},
onClose: () => {
console.log('Checkout closed');
}
});

return (
<div>
<form onSubmit={handleSubmit}>
<label>
Price (cents):
<input
type="number"
value={customPrice ?? ''}
onChange={handlePriceChange}
/>
</label>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating checkout...' : 'Pay'}
</button>
</form>

{checkoutUrl && isEmbedReady && (
<div>
<iframe src={checkoutUrl} />
<button onClick={clearCheckout}>Cancel</button>
</div>
)}
</div>
);
}

Dynamic Params Override

function DynamicCheckout() {
const { handleSubmitWithParams, isLoading } = useCheckoutButton();

const handleUpgrade = async () => {
await handleSubmitWithParams({
variantId: 12345,
metadata: { plan: 'premium', source: 'upgrade-modal' },
dark: true
});
};

return (
<button onClick={handleUpgrade} disabled={isLoading}>
Upgrade to Premium
</button>
);
}