@bettercone/ui
ComponentsPricing

PricingCard

Pricing plan card with auto-checkout via Better Auth Stripe

PricingCard

The PricingCard component displays a pricing plan with automatic Stripe Checkout integration via Better Auth.

v0.3.0 Update: Component now auto-handles checkout with Better Auth Stripe plugin when stripePriceId fields are provided.

Installation

npm install @bettercone/ui

Features

  • Auto-Checkout - Automatic Stripe Checkout when stripePriceId fields provided
  • Custom Callback - Fallback to onSubscribe when no Stripe IDs
  • Billing Interval - Switch between monthly/yearly pricing
  • Popular Badge - Highlight recommended plans
  • Feature Lists - Display plan features with icons
  • Localization - Full i18n support

Usage

import { AuthUIProvider, PricingCard } from '@bettercone/ui';
import { authClient } from '@/lib/auth-client';

export default function PricingPage() {
  return (
    <AuthUIProvider authClient={authClient}>
      <PricingCard
        plan={{
          id: "pro",
          name: "Pro Plan",
          description: "For growing teams",
          priceMonthly: 29,
          priceYearly: 290,
          features: [
            "Unlimited projects",
            "Advanced analytics",
            "Priority support",
          ],
          popular: true,
          
          // Auto-checkout enabled with Stripe IDs
          stripePriceIdMonthly: "price_monthly_xxx",
          stripePriceIdYearly: "price_yearly_xxx",
        }}
        billingInterval="monthly"
      />
    </AuthUIProvider>
  );
}
import { PricingCard } from '@bettercone/ui';

export default function PricingPage() {
  const handleSubscribe = async (planId: string, interval: string) => {
    // Custom checkout flow
    await yourCheckoutFunction(planId, interval);
  };

  return (
    <PricingCard
      plan={{
        id: "pro",
        name: "Pro Plan",
        priceMonthly: 29,
        priceYearly: 290,
        features: ["Feature 1", "Feature 2"],
        // No stripePriceId - uses onSubscribe callback
      }}
      billingInterval="monthly"
      onSubscribe={handleSubscribe}
    />
  );
}
import { PricingCard } from '@bettercone/ui';
import { useState } from 'react';

export default function PricingPage() {
  const [interval, setInterval] = useState<"monthly" | "yearly">("monthly");

  return (
    <>
      <div className="flex gap-2 mb-8">
        <button onClick={() => setInterval("monthly")}>Monthly</button>
        <button onClick={() => setInterval("yearly")}>Yearly</button>
      </div>
      
      <PricingCard
        plan={planData}
        billingInterval={interval}
      />
    </>
  );
}

Props

Prop

Type

Pricing Plan Types

interface PricingPlan {
  id: string;                      // Plan ID (must match Better Auth config)
  name: string;
  description?: string;
  priceMonthly: number;            // Price in dollars
  priceYearly: number;
  currency?: string;               // Default: "USD"
  features: string[];
  popular?: boolean;
  
  // Auto-checkout (optional)
  stripePriceIdMonthly?: string;
  stripePriceIdYearly?: string;
  
  // Limits
  maxUsers?: number;
  maxProjects?: number;
  storageGb?: number;
}

Auto-Checkout Flow

When stripePriceId fields are provided, the component automatically:

  1. Detects Better Auth Stripe plugin via authClient.subscription.upgrade
  2. Calls authClient.subscription.upgrade() with selected plan and interval
  3. Redirects user to Stripe Checkout
  4. Returns to successUrl after payment
// Internal implementation
const handleCheckout = async () => {
  const stripePriceId = billingInterval === "monthly" 
    ? plan.stripePriceIdMonthly 
    : plan.stripePriceIdYearly;

  const result = await authClient.subscription.upgrade({
    planId: plan.id,
    stripePriceId,
    successUrl: successUrl || `${window.location.origin}/billing?success=true`,
    cancelUrl: cancelUrl || window.location.href,
  });

  if (result.data?.url) {
    window.location.href = result.data.url;
  }
};

Examples

Example 1: Pricing Grid

import { AuthUIProvider, PricingCard } from '@bettercone/ui';
import { authClient } from '@/lib/auth-client';

export default function PricingPage() {
  const plans = [
    {
      id: "starter",
      name: "Starter",
      priceMonthly: 9,
      priceYearly: 90,
      features: ["5 projects", "Basic support"],
      stripePriceIdMonthly: "price_starter_monthly",
      stripePriceIdYearly: "price_starter_yearly",
    },
    {
      id: "pro",
      name: "Pro",
      priceMonthly: 29,
      priceYearly: 290,
      features: ["Unlimited projects", "Priority support"],
      popular: true,
      stripePriceIdMonthly: "price_pro_monthly",
      stripePriceIdYearly: "price_pro_yearly",
    },
  ];

  return (
    <AuthUIProvider authClient={authClient}>
      <div className="grid md:grid-cols-2 gap-8">
        {plans.map(plan => (
          <PricingCard key={plan.id} plan={plan} billingInterval="monthly" />
        ))}
      </div>
    </AuthUIProvider>
  );
}

Example 2: With Analytics

<PricingCard
  plan={plan}
  billingInterval={interval}
  onBeforeCheckout={async () => {
    await analytics.track('checkout_started', {
      plan: plan.id,
      interval,
    });
  }}
  successUrl="/billing?success=true&plan=${plan.id}"
/>

Example 3: Custom Styling

<PricingCard
  plan={plan}
  billingInterval="yearly"
  className="hover:shadow-xl transition-shadow"
  classNames={{
    base: "border-2 border-primary",
    header: "bg-gradient-to-r from-blue-500 to-purple-500 text-white",
    price: "text-4xl font-bold",
    features: "space-y-3",
    button: "w-full bg-primary hover:bg-primary/90",
  }}
/>

Migration from v0.2.x

// Before (v0.2.x)
<PricingCard
  plan={planData}
  billingInterval="monthly"
  isLoading={loading}
  onSubscribe={handleSubscribe}
/>

// After (v0.3.0) - Auto-checkout
<AuthUIProvider authClient={authClient}>
  <PricingCard
    plan={{
      ...planData,
      stripePriceIdMonthly: "price_xxx",
      stripePriceIdYearly: "price_yyy",
    }}
    billingInterval="monthly"
  />
</AuthUIProvider>

// After (v0.3.0) - Custom callback (no breaking changes)
<PricingCard
  plan={planData}
  billingInterval="monthly"
  onSubscribe={handleSubscribe}
/>

See Also