Skip to main content

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

usePricingSection

The primary orchestration hook for the entire pricing section. Manages plan selection, billing intervals, checkout flows across multiple payment providers (Stripe, LemonSqueezy, Polar), currency formatting, and embedded payment form state.

Source file: template/hooks/use-pricing-section.ts

Overview

usePricingSection is the largest and most complex pricing hook. It wires together user authentication, payment provider selection, plan configuration, billing interval toggling, checkout session creation, and embedded payment modal management into a single cohesive return object. It supports three payment providers (Stripe, LemonSqueezy, Polar), two billing intervals (monthly/yearly), and both redirect-based and embedded checkout flows.

The hook also supports dynamic pricing from Stripe (fetching products at runtime) and multi-currency pricing through currency-aware variant/price ID resolution.

Signature

function usePricingSection(params?: UsePricingSectionParams): UsePricingSectionReturn

Parameters

interface UsePricingSectionParams {
onSelectPlan?: (plan: PaymentPlan) => void;
initialSelectedPlan?: PaymentPlan | null;
isReview?: boolean;
}
ParameterTypeDefaultDescription
onSelectPlan(plan: PaymentPlan) => voidundefinedCallback invoked when a plan is selected
initialSelectedPlanPaymentPlan | nullnullPre-select a plan on mount. Falls back to STANDARD if paid plans are available, FREE otherwise
isReviewbooleanundefinedWhen true, disables dynamic Stripe product fetching (for review/preview contexts)

Return Value

The hook returns an object conforming to UsePricingSectionReturn, which extends both UsePricingSectionState and UsePricingSectionActions plus additional data fields.

State Properties

interface UsePricingSectionState {
showSelector: boolean;
billingInterval: PaymentInterval;
processingPlan: string | null;
selectedPlan: PaymentPlan | null;
selectedFlow: PaymentFlow;
isButton: boolean;
}
PropertyTypeDescription
showSelectorbooleanWhether the flow selector UI is visible
billingIntervalPaymentIntervalCurrent billing interval ('monthly' or 'yearly')
processingPlanstring | nullThe plan ID currently being processed for checkout, or null
selectedPlanPaymentPlan | nullThe currently selected plan
selectedFlowPaymentFlowThe current payment flow type
isButtonbooleantrue when selectedFlow is 'pay_at_end'

Action Methods

interface UsePricingSectionActions {
setShowSelector: (show: boolean | ((prev: boolean) => boolean)) => void;
setBillingInterval: (interval: PaymentInterval) => void;
setSelectedPlan: (plan: PaymentPlan | null) => void;
handleFlowChange: () => void;
handleFlowSelect: (flow: PaymentFlow) => Promise<void>;
handleSelectPlan: (plan: PaymentPlan) => void;
handleCheckout: (plan: PricingConfig) => Promise<void>;
cancelCurrentProcess: () => void;
calculatePrice: (plan: PricingConfig) => number;
getSavingsText: (plan: PricingConfig) => string | null;
}
MethodDescription
setShowSelectorToggle or set the flow selector visibility
setBillingIntervalSwitch between monthly and yearly billing
setSelectedPlanSet the selected plan directly
handleFlowChangeToggle the flow selector (calls setShowSelector(prev => !prev))
handleFlowSelectSelect a payment flow with animation
handleSelectPlanSelect a plan and fire the onSelectPlan callback
handleCheckoutInitiate the full checkout process for a plan
cancelCurrentProcessCancel any in-progress checkout and reset state
calculatePriceCalculate the price for a plan, applying annual discount if applicable
getSavingsTextGet savings text for yearly billing (e.g., "Save $24/year"), or null if not yearly

Additional Data

PropertyTypeDescription
useranyCurrent authenticated user
configanyApplication configuration
FREEPricingConfigFree plan configuration (static or dynamic from Stripe)
STANDARDPricingConfigStandard plan configuration
PREMIUMPricingConfigPremium plan configuration
providerPaymentProviderActive payment provider ('stripe', 'lemonsqueezy', or 'polar')
freePlanFeaturesPlanFeature[]Feature list for the free plan
standardPlanFeaturesPlanFeature[]Feature list for the standard plan
premiumPlanFeaturesPlanFeature[]Feature list for the premium plan
getPlanConfig(planId: string) => PlanConfigGet plan metadata by ID
getActionText(planId: string) => stringGet CTA text for logged-in users
getNotLoggedInActionText(planId: string) => stringGet CTA text for anonymous users
isLoadingbooleanWhether a checkout session is being created
erroranyCheckout error, if any
isSuccessbooleanWhether checkout session creation succeeded
tfunctionTranslation function for the pricing namespace
tBillingfunctionTranslation function for the billing namespace
routerAppRouterInstanceNext.js router instance
loginModalLoginModalStoreLogin modal state and controls
currencystringCurrent currency code (e.g., 'USD', 'EUR')
currencySymbolstringCurrency symbol (e.g., '$', '\u20ac')
formatPrice(amount: number) => stringFormat a price with the current currency symbol
clientSecretstring | nullStripe SetupIntent client secret for embedded mode
isReadybooleanWhether the Stripe SetupIntent is ready

Payment Form Modal

The paymentForm object manages the embedded payment form modal state:

paymentForm: {
isOpen: boolean;
planForPayment: PricingConfig | null;
openPaymentForm: (plan: PricingConfig) => void;
closePaymentForm: () => void;
onPaymentSuccess: (paymentMethodId: string) => void;
onPaymentError: (error: Error) => void;
clientSecret?: string;
checkoutUrl?: string | null;
isReady?: boolean;
isError?: boolean;
}

Implementation Details

Checkout Flow

The handleCheckout method follows this flow:

  1. Authentication check: If no user is logged in, opens the login modal with a prompt message.
  2. Cancel previous: If a different plan is already being processed, cancels it first.
  3. Provider routing: Routes to the appropriate payment provider:
    • Stripe: Uses embedded mode (SetupIntent + subscription creation) or redirect mode (checkout session).
    • LemonSqueezy: Resolves currency-aware variant IDs from getLemonSqueezyPriceConfig, then creates a checkout session. Supports embedded overlay mode.
    • Polar: Resolves currency-aware price IDs from getPolarPriceConfig, then creates a checkout session. Supports embedded mode.
  4. Error handling: Logs errors and shows toast notifications on failure.

Dynamic Pricing (Stripe)

When using Stripe with dynamic pricing enabled, the hook fetches products from the Stripe API at runtime using useStripeProducts. Dynamic product data is merged with static config, with dynamic values taking precedence.

Price Calculation

calculatePrice applies annual discounts when the billing interval is yearly:

annualPrice = monthlyPrice * 12
discountedPrice = round(annualPrice * (1 - annualDiscount / 100))

Plan Validation

The internal validateAndMapPlanName function ensures plan IDs are valid before being passed to billing configuration lookups. It throws an error for unrecognized plan IDs.

Usage Examples

Basic pricing section

import { usePricingSection } from '@/hooks/use-pricing-section';

function PricingSection() {
const pricing = usePricingSection();

return (
<div>
<BillingToggle
interval={pricing.billingInterval}
onChange={pricing.setBillingInterval}
/>
{[pricing.FREE, pricing.STANDARD, pricing.PREMIUM]
.filter(Boolean)
.map((plan) => (
<PlanCard
key={plan.id}
plan={plan}
features={pricing.getFeaturesByPlan?.(plan.id) ?? []}
price={pricing.formatPrice(pricing.calculatePrice(plan))}
savings={pricing.getSavingsText(plan)}
isProcessing={pricing.processingPlan === plan.id}
onCheckout={() => pricing.handleCheckout(plan)}
actionText={
pricing.user
? pricing.getActionText(plan.id)
: pricing.getNotLoggedInActionText(plan.id)
}
/>
))}
</div>
);
}

With embedded payment form

function PricingWithEmbeddedPayment() {
const pricing = usePricingSection();
const { paymentForm } = pricing;

return (
<>
<PricingCards pricing={pricing} />

{paymentForm.isOpen && (
<PaymentModal
plan={paymentForm.planForPayment}
clientSecret={paymentForm.clientSecret}
checkoutUrl={paymentForm.checkoutUrl}
onSuccess={paymentForm.onPaymentSuccess}
onError={paymentForm.onPaymentError}
onClose={paymentForm.closePaymentForm}
/>
)}
</>
);
}

Pre-selected plan from URL

function PricingPage() {
const pricing = usePricingSection({
initialSelectedPlan: PaymentPlan.PREMIUM,
onSelectPlan: (plan) => {
console.log('Plan selected:', plan);
},
});

// The hook also reads `?plan=` from the URL search params
// to auto-select a plan from external links
}

Requirements

DependencyPurpose
@tanstack/react-queryCheckout session mutation state
next/navigationRouter and search params
next-intlTranslations for pricing and billing
next-themesTheme detection for dark mode checkout
sonnerToast notifications
@/hooks/use-pricing-featuresPlan features and config
@/hooks/use-payment-flowPayment flow selection with animations
@/hooks/use-create-checkoutStripe checkout session creation
@/hooks/use-checkout-buttonLemonSqueezy checkout
@/hooks/use-polar-checkoutPolar checkout
@/hooks/use-selected-checkout-providerUser's preferred payment provider
@/hooks/use-current-userCurrent user data
@/hooks/use-setup-intentStripe SetupIntent for embedded payments
@/hooks/use-subscriptionSubscription creation
@/hooks/use-payment-availabilityDetermine if paid plans should be shown
@/hooks/use-stripe-productsDynamic Stripe product fetching
@/hooks/use-login-modalLogin modal state
@/components/context/currency-providerCurrent currency context
@/lib/config/billing/*Currency-aware billing configurations