Simple subscription-based access control for AI features. Users with active subscriptions get unlimited access to AI features.

Overview

The subscription access control system (src/features/ai/server.ts) provides:

  • Subscription Checking - Verify user has active subscription
  • Tier Detection - Determine user's subscription tier (premium/free)
  • Access Control - Gate AI features behind subscription requirement

Architecture

The system uses ShipSafe core's subscription management:

┌─────────────────────────────────────┐ │ AI-SaaS Application │ │ - API Routes │ │ - UI Components │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ AI Features Layer │ │ - hasActiveSubscription() │ │ - getSubscriptionTier() │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ ShipSafe Core │ │ - getUserSubscriptionStatus() │ │ - Subscription models │ └─────────────────────────────────────┘

Basic Usage

Check Subscription in API Routes

import { hasActiveSubscription } from "@/features/ai/server";
import { requireAuth } from "@core/src/lib/firebase/auth";

export async function POST(req: NextRequest) {
  const user = await requireAuth(req);
  
  // Check if user has active subscription
  const hasAccess = await hasActiveSubscription(user.uid);
  
  if (!hasAccess) {
    return NextResponse.json(
      {
        error: "Subscription required",
        message: "Please upgrade your plan to access AI features.",
      },
      { status: 403 }
    );
  }
  
  // Proceed with AI request...
}

Get Subscription Tier

import { getSubscriptionTier } from "@/features/ai/server";

const tier = await getSubscriptionTier(userId);
// Returns: "premium" | "free"

// Use tier for tiered prompts or model selection
const systemPrompt = tier === "premium" 
  ? ADVANCED_TIER_PROMPT 
  : BASIC_TIER_PROMPT;

Subscription Status

The system checks for these subscription statuses:

  • "active" - Active subscription (unlimited access)
  • "trialing" - Trial period active (unlimited access)
  • "inactive" - No subscription or canceled (no access)

Tiered System

Free Tier

  • No active subscription
  • No access to AI features
  • Upgrade prompt shown

Premium Tier

  • Active subscription (including trials)
  • Unlimited AI access
  • Advanced prompts and models

Implementation Example

In API Route

import { NextRequest, NextResponse } from "next/server";
import { requireAuth } from "@core/src/lib/firebase/auth";
import { hasActiveSubscription, getSubscriptionTier } from "@/features/ai/server";
import { generateChatCompletion } from "@/lib/ai/client";
import { buildMessageHistoryWithTier } from "@/lib/ai/prompts";

export async function POST(req: NextRequest) {
  try {
    const user = await requireAuth(req);
    
    // Check subscription
    const hasAccess = await hasActiveSubscription(user.uid);
    if (!hasAccess) {
      return NextResponse.json(
        {
          error: "Subscription required",
          message: "Please upgrade your plan to access AI features.",
        },
        { status: 403 }
      );
    }
    
    // Get tier for tiered prompts
    const tier = await getSubscriptionTier(user.uid);
    
    // Build message history with tier-appropriate prompts
    const body = await req.json();
    const messages = buildMessageHistoryWithTier(body.messages || [], { tier });
    
    // Generate AI response
    const response = await generateChatCompletion({ messages });
    
    return NextResponse.json({ data: response });
  } catch (error) {
    return NextResponse.json(
      { error: "Server error" },
      { status: 500 }
    );
  }
}

Tiered Prompts

The system supports different system prompts based on subscription tier:

Basic Tier Prompt

Used for free tier users (if free tier is enabled):

  • Friendly and approachable tone
  • Fundamental concepts explained simply
  • Practical, actionable advice
  • Step-by-step guidance

Advanced Tier Prompt

Used for premium subscribers:

  • Professional and technical tone
  • In-depth technical analysis
  • Advanced concepts and methodologies
  • Comprehensive solutions

See Prompts documentation for details.

UI Integration

Show Subscription Status

import SubscriptionStatus from "@/components/ai/SubscriptionStatus";

export function Dashboard() {
  return (
    <div>
      <SubscriptionStatus />
      <ChatInterface />
    </div>
  );
}

Disable Chat for Non-Subscribers

"use client";

import { useState, useEffect } from "react";
import { doc, onSnapshot } from "firebase/firestore";
import { getFirestoreInstance } from "@core/src/lib/firebase/client";
import ChatInterface from "@/components/ai/ChatInterface";

export function ChatPage() {
  const [hasSubscription, setHasSubscription] = useState(false);
  
  useEffect(() => {
    // Listen to user's subscription status
    const db = getFirestoreInstance();
    const userRef = doc(db, "users", userId);
    
    const unsubscribe = onSnapshot(userRef, (doc) => {
      const subscription = doc.data()?.subscription;
      const isActive = subscription?.status === "active" || subscription?.status === "trialing";
      setHasSubscription(isActive);
    });
    
    return () => unsubscribe();
  }, [userId]);
  
  return (
    <ChatInterface 
      disabled={!hasSubscription}
      placeholder={hasSubscription ? "Ask your AI assistant..." : "Upgrade to continue chatting"}
    />
  );
}

Best Practices

  1. Check server-side - Always verify subscription in API routes, never trust client
  2. Show clear CTAs - Display upgrade prompts for non-subscribers
  3. Use tiered prompts - Provide better experience for premium users
  4. Handle gracefully - Show friendly error messages, not technical errors
  5. Update in real-time - Use Firestore listeners for live subscription updates

Optional Free Tier

If you want to enable a limited free tier:

  1. Modify hasActiveSubscription() to allow free tier users
  2. Add free tier limits (e.g., 10 messages per day)
  3. Track usage in Firestore
  4. Show usage limits in UI

The current implementation requires a subscription for all AI features, keeping it simple and aligned with the subscription-based model.

Next Steps