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
- Check server-side - Always verify subscription in API routes, never trust client
- Show clear CTAs - Display upgrade prompts for non-subscribers
- Use tiered prompts - Provide better experience for premium users
- Handle gracefully - Show friendly error messages, not technical errors
- Update in real-time - Use Firestore listeners for live subscription updates
Optional Free Tier
If you want to enable a limited free tier:
- Modify
hasActiveSubscription()to allow free tier users - Add free tier limits (e.g., 10 messages per day)
- Track usage in Firestore
- 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
- Learn about Tiered Prompts
- Explore AI Client
- Check out Subscription Status Component
- Review Billing Features for subscription management