@bettercone/ui
ComponentsBilling

SubscriptionCard

Display and manage active subscription with Better Auth Stripe integration

SubscriptionCard

The SubscriptionCard component displays the user's current subscription plan with management actions including access to the Stripe Customer Portal.

v0.3.0 Update: Component now supports auto-fetch mode with Better Auth Stripe plugin. No authClient prop required - just wrap with AuthUIProvider.

Installation

npm install @bettercone/ui

Features

  • Auto-Fetch Mode - Automatically fetches subscription from Better Auth Stripe plugin
  • Presentational Mode - Pass subscription data manually via data prop
  • Billing Portal - One-click access to Stripe Customer Portal
  • Status Badges - Visual indicators (active, trialing, past_due, canceled)
  • Organization Support - Auto-detects organization subscriptions
  • Localization - Full i18n support with custom labels
  • Type-Safe - Full TypeScript support

Usage

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

export default function BillingPage() {
  return (
    <AuthUIProvider authClient={authClient}>
      {/* Zero config - auto-fetches subscription */}
      <SubscriptionCard />
    </AuthUIProvider>
  );
}
import { SubscriptionCard, type Subscription } from '@bettercone/ui';

export default function BillingPage() {
  const subscription: Subscription = {
    id: "sub_123",
    plan: "pro",
    status: "active",
    referenceId: userId,
    currentPeriodEnd: new Date("2025-12-01"),
    seats: 5,
  };

  return (
    <SubscriptionCard 
      data={subscription}
      viewPlansUrl="/pricing"
    />
  );
}
import { AuthUIProvider, SubscriptionCard } from '@bettercone/ui';
import { authClient } from '@/lib/auth-client';

export default function BillingPage() {
  return (
    <AuthUIProvider authClient={authClient}>
      <SubscriptionCard
        onBeforeManage={async () => {
          // Track analytics, show warning, etc.
          console.log('Opening billing portal...');
        }}
        viewPlansUrl="/pricing?from=billing"
      />
    </AuthUIProvider>
  );
}
import { AuthUIProvider, SubscriptionCard } from '@bettercone/ui';
import { authClient } from '@/lib/auth-client';

export default function BillingPage() {
  return (
    <AuthUIProvider authClient={authClient}>
      <SubscriptionCard
        localization={{
          currentPlan: "Plano Atual",
          manageBilling: "Gerenciar Cobrança",
          noActiveSubscription: "Nenhuma assinatura ativa",
          viewPlans: "Ver Planos",
        }}
      />
    </AuthUIProvider>
  );
}

Props

Prop

Type

Component Modes

Requires Better Auth Stripe plugin. Component automatically fetches subscription data.

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

<AuthUIProvider authClient={authClient}>
  <SubscriptionCard />
</AuthUIProvider>

Requirements:

  • Better Auth Stripe plugin installed (@better-auth/stripe)
  • AuthUIProvider wrapping component
  • User authenticated via Better Auth

Mode B: Presentational

Pass subscription data manually. No Better Auth dependency.

import { SubscriptionCard, type Subscription } from '@bettercone/ui';

const subscription: Subscription = {
  id: "sub_123",
  plan: "pro",
  status: "active",
  referenceId: userId,
  currentPeriodEnd: new Date("2025-12-01"),
};

<SubscriptionCard data={subscription} />

Subscription Types

interface Subscription {
  id: string;
  plan: string;                    // Plan ID (e.g., "pro", "enterprise")
  referenceId: string;             // User or org ID
  status: SubscriptionStatus;
  currentPeriodStart?: Date;
  currentPeriodEnd?: Date;
  cancelAtPeriodEnd?: boolean;
  cancelAt?: Date;
  seats?: number;                  // Seat limit for team plans
  trialStart?: Date;
  trialEnd?: Date;
  stripeCustomerId?: string;
  stripeSubscriptionId?: string;
}

type SubscriptionStatus = 
  | "incomplete"
  | "incomplete_expired"
  | "trialing"
  | "active"
  | "past_due"
  | "canceled"
  | "unpaid";

Subscription Detection

In auto-fetch mode, the component automatically detects subscriptions by:

  1. Personal Subscriptions: Matches subscription.referenceId to user.id
  2. Organization Subscriptions: Matches subscription.referenceId to active organization
  3. Active Status: Filters for active or trialing subscriptions
// Auto-detection logic (internal)
const validReferenceIds = [
  session.user.id,                    // Personal
  activeOrganization?.id,             // Current org
];

const subscription = subscriptions.find(
  sub => validReferenceIds.includes(sub.referenceId) &&
         (sub.status === "active" || sub.status === "trialing")
);

Status Badges

The component displays different UI based on subscription status:

StatusBadge ColorDescription
activeGreenSubscription is active and paid
trialingBlueIn free trial period
past_dueYellowPayment failed, retry in progress
canceledRedSubscription canceled, active until period end
incompleteOrangeRequires payment action

Billing Portal Integration

The "Manage Billing" button opens Stripe's Customer Portal using authClient.subscription.billingPortal().

// Internal implementation
const handleManage = async () => {
  const result = await authClient.subscription.billingPortal({
    referenceId: subscription.referenceId,
    returnUrl: window.location.href,
  });
  
  if (result.data?.url) {
    window.location.href = result.data.url;
  }
};

The Stripe Customer Portal must be enabled in your Stripe Dashboard settings.

Examples

Example 1: Billing Dashboard

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

export default function BillingPage() {
  return (
    <AuthUIProvider authClient={authClient}>
      <div className="container mx-auto py-8 space-y-6">
        <h1 className="text-3xl font-bold">Billing</h1>
        
        <div className="grid gap-6 lg:grid-cols-2">
          <div className="lg:col-span-2">
            <SubscriptionCard />
          </div>
          
          <PaymentMethodCard />
          <InvoiceHistoryCard />
        </div>
      </div>
    </AuthUIProvider>
  );
}

Example 2: Organization Billing

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

export default function OrgBillingPage() {
  return (
    <AuthUIProvider authClient={authClient}>
      {/* Auto-detects active organization subscription */}
      <SubscriptionCard 
        viewPlansUrl="/org/pricing"
        onBeforeManage={async () => {
          // Log analytics event
          await analytics.track('org_billing_portal_opened');
        }}
      />
    </AuthUIProvider>
  );
}

Example 3: Read-Only Display

import { SubscriptionCard, type Subscription } from '@bettercone/ui';

export default function AccountOverview() {
  const subscription: Subscription = {
    id: "sub_123",
    plan: "pro",
    status: "active",
    referenceId: userId,
    currentPeriodEnd: new Date("2025-12-01"),
  };

  return (
    <SubscriptionCard 
      data={subscription}
      showActions={false}
      className="border-muted"
    />
  );
}

Customization

Custom Styling

<SubscriptionCard 
  className="shadow-lg"
  classNames={{
    base: "border-2",
    header: "bg-primary text-primary-foreground",
    content: "bg-muted/50 p-6",
    footer: "border-t",
  }}
/>

Custom Localization

const frenchLocalization = {
  currentPlan: "Plan Actuel",
  status: "Statut",
  renewsOn: "Renouvelle le",
  endsOn: "Se termine le",
  manageBilling: "Gérer la Facturation",
  cancelSubscription: "Annuler l'Abonnement",
  noActiveSubscription: "Aucun abonnement actif",
  viewPlans: "Voir les Plans",
  loading: "Chargement...",
  error: "Erreur de chargement",
};

<SubscriptionCard localization={frenchLocalization} />

Migration from v0.2.x

Breaking Change: The authClient prop has been removed. Use AuthUIProvider instead.

// Before (v0.2.x)
<SubscriptionCard authClient={authClient} />

// After (v0.3.0) - Auto-fetch
<AuthUIProvider authClient={authClient}>
  <SubscriptionCard />
</AuthUIProvider>

// After (v0.3.0) - Presentational
<SubscriptionCard data={subscriptionData} />

See Also