web-production-saas-starter/next_b2b_starter/docs/04-payments-and-billing.md
2025-12-16 18:54:41 +04:00

2.2 KiB

Payments & Billing

The starter supports Stripe or Polar. Subscription status is managed via Stytch Custom Claims.

Subscription Status

  • active / trialing: User has access.
  • past_due / canceled / unpaid: Access restricted.

Checking Payment Status

Use hasActiveSubscription() helper function.

File: lib/auth/subscription.ts

Server-Side Check

import { hasActiveSubscription } from '@/lib/auth/subscription';

export default async function Page() {
  const session = await requireMemberSession();
  if (!hasActiveSubscription(session)) {
    return <div>Upgrade to access</div>;
  }
  return <PremiumContent />;
}

Client-Side Check

'use client';
import { hasActiveSubscription } from '@/lib/auth/subscription';

export function Feature() {
  const { session, member } = useStytchMemberSession();
  if (!hasActiveSubscription(session, member)) {
    return <button>Subscribe</button>;
  }
  return <FeatureContent />;
}

Payment Flow Architecture

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant Stripe/Polar

    User->>Frontend: Click Subscribe
    Frontend->>API: POST /api/billing/checkout
    API->>Stripe/Polar: Create Session
    Stripe/Polar-->>User: Redirect to Checkout
    User->>Stripe/Polar: Pay
    Stripe/Polar->>API: Webhook (async)
    API->>Stytch: Update Custom Claims

Paywalls

We include a pre-built Paywall component.

File: components/billing/subscription-paywall.tsx

import { SubscriptionPaywall } from '@/components/billing/subscription-paywall';

export default function Page() {
  return (
    <SubscriptionPaywall>
      <ProtectedContent />
    </SubscriptionPaywall>
  );
}

API Route Protection

Always verify logic in your API routes, returning 402 Payment Required if needed.

if (!hasActiveSubscription(session)) {
  return NextResponse.json({ error: 'Upgrade required' }, { status: 402 });
}

Webhooks

Webhooks handle status updates asynchronously.

File: app/api/billing/webhook/route.ts

Next Steps

👉 Learn about: Making API Requests