Skip to main content

Subscription Hooks

The Ever Works Template provides a set of React hooks for managing subscriptions across three payment providers: Stripe, LemonSqueezy, and Polar. These hooks handle subscription creation, cancellation, reactivation, auto-renewal toggling, plan access checks, and billing data retrieval.

Architecture Overview

Source Files

FilePurpose
hooks/use-subscription.tsStripe subscription CRUD + billing portal
hooks/use-auto-renewal.tsAuto-renewal toggle with optimistic updates
hooks/use-lemonsqueezy-subscription.tsLemonSqueezy plan changes, pause, resume
hooks/use-polar-subscription.tsPolar cancel and reactivate
hooks/use-plan-status.tsPlan access checks and expiration awareness
hooks/use-billing-data.tsBilling history and subscription info queries

Provider-Specific Hooks

useSubscription (Stripe)

The primary Stripe subscription hook. Provides mutations for the full subscription lifecycle and a billing portal session creator.

function useSubscription(): UseSubscriptionReturn

Mutations:

MutationMethodEndpointDescription
createSubscriptionPOST/api/stripe/subscriptionCreate a new subscription
updateSubscriptionPUT/api/stripe/subscriptionUpdate plan or billing interval
cancelSubscriptionDELETE/api/stripe/subscriptionCancel (supports period-end)
reactivateSubscriptionPOST/api/stripe/subscription/:id/reactivateReactivate a cancelled sub
createBillingPortalSessionPOST/api/stripe/subscription/portal or /api/polar/subscription/portalOpen provider billing portal

The billing portal endpoint is determined automatically based on the active payment provider (Stripe or Polar), using usePaymentProvider and useSelectedCheckoutProvider internally.

Related query hooks:

HookQuery KeyPurpose
useUserSubscription()['user-subscription']Fetch the current user's subscription
useSubscriptionById(id)['subscription', id]Fetch a specific subscription
useSubscriptionManager()--Wraps useSubscription with optimistic create

useSubscriptionActions (LemonSqueezy)

Manages LemonSqueezy-specific subscription operations including pause and resume.

function useSubscriptionActions(): SubscriptionActionsReturn

Mutations:

MutationEndpointDescription
updatePlan/api/lemonsqueezy/update-planChange subscription variant
cancelSubscription/api/lemonsqueezy/cancelCancel subscription
pauseSubscription/api/lemonsqueezy/pausePause (void or free mode)
resumeSubscription/api/lemonsqueezy/resumeResume paused subscription
reactivateSubscription/api/lemonsqueezy/reactivateReactivate cancelled subscription

All mutations invalidate ['lemonsqueezy-subscriptions'] and ['lemonsqueezy-stats'] on success.

usePolarSubscription

Handles Polar subscription cancellation and reactivation with retry logic and toast notifications.

function usePolarSubscription(): PolarSubscriptionReturn
FunctionEndpointDescription
cancel(id, cancelAtPeriodEnd?)/api/polar/subscription/:id/cancelCancel with period-end option
reactivate(id)/api/polar/subscription/:id/reactivateReactivate cancelled subscription

The hook implements exponential backoff retry (up to 2 retries) and skips retrying on authentication errors. A simplified useCancelPolarSubscription hook is also exported for direct component use.

Auto-Renewal Management

useAutoRenewal

Manages the auto-renewal toggle for any payment provider, with optimistic updates and rollback on error.

function useAutoRenewal(options: UseAutoRenewalOptions): UseAutoRenewalReturn

Options:

OptionTypeDefaultDescription
subscriptionIdstring--The subscription to manage
enabledbooleantrueWhether the query is active
onSuccess(data) => void--Called when status loads successfully
onUpdateSuccess(data) => void--Called after successful toggle

Key return fields:

FieldTypeDescription
autoRenewalboolean | undefinedCurrent auto-renewal state
cancelAtPeriodEndbooleanWhether subscription ends at period
paymentProviderPaymentProviderDetected payment provider
toggleAutoRenewal() => voidToggle the renewal state
enableAutoRenewal() => voidEnable auto-renewal
disableAutoRenewal() => voidDisable auto-renewal

Optimistic update flow:

  1. Cancel outgoing refetches for the query key.
  2. Snapshot the current cache value.
  3. Optimistically set the new value in cache.
  4. On success: update cache with server response, invalidate related queries, show toast.
  5. On error: rollback to snapshot, show error toast, refetch for consistency.

Plan Access Hooks

usePlanStatus

Fetches the user's current plan with expiration awareness. Returns computed properties for feature gating.

function usePlanStatus(): PlanStatus
FieldTypeDescription
planIdstringRaw plan identifier
effectivePlanstringPlan after expiration rules applied
isExpiredbooleanWhether the plan has expired
expiresAtDate | nullExpiration date
daysUntilExpirationnumber | nullDays remaining
isInWarningPeriodbooleanNear expiration
canAccessPlanFeaturesbooleanWhether features are accessible
warningMessagestring | nullUser-facing warning text

Defaults to PaymentPlan.FREE for unauthenticated users. The query is only enabled when a user session exists.

usePlanAccess

Checks whether the user's effective plan meets a minimum required level.

function usePlanAccess(requiredPlan: string): PlanAccessResult
FieldTypeDescription
hasAccessbooleanWhether the user meets the plan requirement
reason'allowed' | 'insufficient_plan' | 'expired' | 'loading'Why access is or is not granted

Plan levels are ranked: free (1) < standard (2) < premium (3). Unknown plans default to the highest required level as a fail-safe.

Billing Data

useBillingData

Fetches subscription info and payment history in parallel queries.

function useBillingData(): BillingDataReturn
QueryEndpointStale TimeDescription
Subscription/api/user/subscription5 minutesCurrent and historical subscriptions
Payments/api/user/payments10 minutesPayment transaction history

Cache management methods:

MethodDescription
refresh()Refetch both subscription and payments
refreshSubscription()Refetch subscription only
refreshPayments()Refetch payments only
invalidateCache()Mark all billing queries as stale
clearCache()Remove all billing data from cache

useOptimisticSubscriptionUpdate

Provides optimistic update and rollback utilities for subscription data.

function useOptimisticSubscriptionUpdate(): OptimisticUpdateReturn
MethodDescription
updateSubscriptionOptimistically(updates)Merge partial updates into cached subscription
revertSubscriptionUpdate()Invalidate cache to refetch from server

usePrefetchBillingData

Preloads billing data into the React Query cache before navigation.

function usePrefetchBillingData(): PrefetchReturn

Returns prefetchSubscription(), prefetchPayments(), and prefetchAll() functions.

Cache Invalidation Strategy

All subscription hooks follow a consistent cache invalidation pattern on mutation success:

queryClient.invalidateQueries({ queryKey: ['subscriptions'] });
queryClient.invalidateQueries({ queryKey: ['user-subscription'] });
queryClient.invalidateQueries({ queryKey: ['billing'] });

This ensures that subscription changes propagate to all parts of the UI that display subscription state.

Further Reading