/template recipe-per-seat-billing
Commercial License Required
MakerKit requires a commercial license. While Orchestre can help you build with it, you must obtain a valid license from MakerKit for commercial use.
Access: /template recipe-per-seat-billing or /t recipe-per-seat-billing
Purpose
Implements a comprehensive per-seat billing model where teams pay based on the number of active users. This recipe creates infrastructure for seat management, invitation flows, billing calculations, and automatic scaling of subscriptions.
How It Actually Works
The recipe:
- Analyzes your current team and billing structure
- Creates seat tracking and management systems
- Implements invitation and onboarding flows
- Adds seat-based pricing calculations
- Integrates with Stripe for dynamic subscriptions
- Sets up usage reporting and analytics
Use Cases
- Business SaaS: Teams pay per active member
- Collaboration Tools: Workspace seat management
- Enterprise Software: Department-based licensing
- Development Platforms: Per-developer pricing
- Educational Tools: Per-student subscriptions
- Design Tools: Per-designer seat allocation
Examples
Basic Per-Seat Model
bash
/template recipe-per-seat-billing
# Implements standard per-seat billing with:
# - $10/user/month pricing
# - Unlimited invitations
# - Auto-scaling subscriptions
# - Seat usage analyticsTiered Seat Pricing
bash
/template recipe-per-seat-billing tiered
# Creates volume-based pricing with:
# - 1-5 seats: $15/seat
# - 6-20 seats: $12/seat
# - 21+ seats: $10/seat
# - Bulk discountsRole-Based Seats
bash
/template recipe-per-seat-billing role-based
# Advanced seat system with:
# - Admin seats: $20/month
# - Editor seats: $15/month
# - Viewer seats: $5/month
# - Custom role pricingWhat Gets Created
Database Schema
sql
-- Seat allocations table
create table team_seats (
id uuid primary key default gen_random_uuid(),
team_id uuid references teams(id) on delete cascade,
subscription_id text references subscriptions(id),
total_seats integer not null default 1,
used_seats integer not null default 0,
seat_type text default 'standard',
price_per_seat decimal(10,2) not null,
created_at timestamp with time zone default now(),
updated_at timestamp with time zone default now()
);
-- Seat assignments
create table seat_assignments (
id uuid primary key default gen_random_uuid(),
team_id uuid references teams(id) on delete cascade,
user_id uuid references auth.users(id) on delete cascade,
seat_type text default 'standard',
role text not null,
assigned_at timestamp with time zone default now(),
assigned_by uuid references auth.users(id),
last_active_at timestamp with time zone,
is_billable boolean default true,
metadata jsonb default '{}'
);
-- Pending invitations
create table seat_invitations (
id uuid primary key default gen_random_uuid(),
team_id uuid references teams(id) on delete cascade,
email text not null,
role text not null,
seat_type text default 'standard',
invited_by uuid references auth.users(id),
token text unique not null,
expires_at timestamp with time zone not null,
accepted_at timestamp with time zone,
created_at timestamp with time zone default now()
);
-- Seat usage history
create table seat_usage_history (
id uuid primary key default gen_random_uuid(),
team_id uuid references teams(id) on delete cascade,
period_start timestamp with time zone not null,
period_end timestamp with time zone not null,
average_seats decimal(10,2),
peak_seats integer,
total_cost decimal(10,2),
created_at timestamp with time zone default now()
);API Routes
typescript
// app/api/seats/manage/route.ts
POST /api/seats/manage
- Add/remove seats
- Update subscription quantity
- Calculate prorated charges
// app/api/seats/invite/route.ts
POST /api/seats/invite
- Send seat invitations
- Check seat availability
- Generate invitation tokens
// app/api/seats/assign/route.ts
POST /api/seats/assign
- Assign user to seat
- Update seat usage
- Trigger billing updates
// app/api/seats/usage/route.ts
GET /api/seats/usage
- Current seat utilization
- Historical usage data
- Cost projectionsReact Components
typescript
// components/billing/seat-manager.tsx
- Visual seat allocation
- Add/remove seat controls
- Usage indicators
- Cost calculator
// components/billing/seat-invitation-form.tsx
- Bulk invitation UI
- Role selection
- Seat type assignment
- Preview costs
// components/billing/seat-usage-chart.tsx
- Usage over time
- Cost trends
- Seat utilization
- Forecast display
// components/team/member-seats-table.tsx
- Member list with seats
- Role management
- Last active status
- Remove member actionHooks and Utilities
typescript
// hooks/use-seats.ts
- useSeatAllocation()
- useSeatUsage()
- useSeatInvitations()
- useSeatCosts()
// lib/seats/calculator.ts
- Calculate seat costs
- Proration logic
- Tiered pricing
- Discount application
// lib/seats/manager.ts
- Seat assignment
- Availability checking
- Auto-scaling logic
- Usage trackingTechnical Details
Dynamic Subscription Updates
typescript
// Stripe subscription quantity updates
async function updateSeatCount(teamId: string, newSeatCount: number) {
const team = await getTeam(teamId);
const subscription = await stripe.subscriptions.retrieve(team.subscriptionId);
// Update quantity with proration
await stripe.subscriptions.update(subscription.id, {
items: [{
id: subscription.items.data[0].id,
quantity: newSeatCount,
}],
proration_behavior: 'create_prorations',
});
// Update local records
await updateTeamSeats(teamId, newSeatCount);
}Seat Assignment Logic
typescript
// Intelligent seat assignment
async function assignSeat(teamId: string, userId: string, role: string) {
const seats = await getTeamSeats(teamId);
if (seats.used >= seats.total) {
// Auto-scale if enabled
if (seats.autoScale) {
await addSeats(teamId, 1);
} else {
throw new Error('No available seats');
}
}
// Assign the seat
await createSeatAssignment({
teamId,
userId,
role,
seatType: determineSeatType(role),
});
// Update usage
await incrementSeatUsage(teamId);
}Usage Tracking
typescript
// Track seat utilization
const seatUsageTracker = {
async recordDailyUsage(teamId: string) {
const usage = await calculateCurrentUsage(teamId);
await saveDailySnapshot({
teamId,
date: new Date(),
usedSeats: usage.active,
totalSeats: usage.total,
utilization: usage.percentage,
});
},
async generateMonthlyReport(teamId: string) {
const usage = await getMonthlyUsage(teamId);
return {
averageUtilization: usage.average,
peakUsage: usage.peak,
recommendedSeats: usage.recommended,
potentialSavings: usage.savings,
};
}
};Memory Evolution
The recipe creates detailed seat management memory:
markdown
## Per-Seat Billing Configuration
### Pricing Model
- Base price: $10/seat/month
- Minimum seats: 1
- Auto-scaling: Enabled
- Proration: Enabled
### Seat Types
- Standard: $10/month (full access)
- Limited: $5/month (read-only)
- Guest: Free (limited features)
### Current Policies
- Unused seats: Retained for 30 days
- Invitation expiry: 7 days
- Seat reassignment: Immediate
- Billing cycle: Monthly
### Integration Status
- Stripe: Subscription quantity sync
- Analytics: Daily usage tracking
- Notifications: Seat limit warnings
- Admin tools: Bulk seat managementBest Practices
Flexible Seat Management
- Allow seat pooling across teams
- Implement seat reservations
- Support temporary seat increases
- Enable seat sharing for part-time users
Cost Optimization
- Show real-time cost implications
- Provide usage recommendations
- Alert on underutilized seats
- Suggest optimal seat counts
User Experience
- Clear seat availability indicators
- Smooth invitation flows
- Self-service seat management
- Transparent pricing display
Compliance
- Audit trail for seat changes
- Usage reports for finance
- License compliance tracking
- Export capabilities
Integration Points
With Team Management
typescript
// Automatic seat assignment on team join
const addTeamMember = async (teamId, userId, role) => {
await assignSeat(teamId, userId, role);
await addMemberToTeam(teamId, userId, role);
await notifyTeamOwner(teamId, 'New member added');
};With Billing System
typescript
// Seat-based invoice generation
const generateInvoice = async (teamId) => {
const seatUsage = await calculateSeatUsage(teamId);
const amount = seatUsage.seats * seatUsage.pricePerSeat;
return createInvoice({
teamId,
lineItems: [{
description: `${seatUsage.seats} seats`,
amount,
}],
});
};With Analytics
typescript
// Seat utilization metrics
const seatMetrics = {
utilization: async (teamId) => {
const { used, total } = await getSeatUsage(teamId);
return (used / total) * 100;
},
costPerActiveUser: async (teamId) => {
const cost = await getMonthlyCost(teamId);
const activeUsers = await getActiveUserCount(teamId);
return cost / activeUsers;
},
};Troubleshooting
Common Issues
"No Available Seats" Error
- Check current seat allocation
- Verify auto-scaling settings
- Review seat assignment logs
- Ensure billing is current
Subscription Sync Issues
- Verify Stripe webhook configuration
- Check subscription item IDs
- Review proration settings
- Validate quantity updates
Invitation Problems
- Check email delivery
- Verify invitation tokens
- Review expiration settings
- Test invitation flow
Debug Helpers
typescript
// Seat debugging utilities
const seatDebug = {
async checkAvailability(teamId: string) {
const seats = await getTeamSeats(teamId);
console.log('Seat Status:', {
total: seats.total,
used: seats.used,
available: seats.total - seats.used,
assignments: await getSeatAssignments(teamId),
});
},
};Advanced Features
Seat Forecasting
typescript
// ML-based seat prediction
const forecastSeats = async (teamId: string) => {
const history = await getSeatHistory(teamId);
const growth = calculateGrowthRate(history);
return {
next30Days: predictSeats(30, growth),
next90Days: predictSeats(90, growth),
recommendedSeats: optimalSeatCount(history),
};
};Bulk Operations
typescript
// Bulk seat management
const bulkOperations = {
async importUsers(teamId: string, csvData: string) {
const users = parseCSV(csvData);
const results = await Promise.allSettled(
users.map(user => assignSeat(teamId, user))
);
return summarizeResults(results);
},
};Related Commands
/template add-team-management- Enhanced team features/template add-billing- Core billing setup/template add-usage-analytics- Detailed usage tracking/template add-admin-panel- Seat management UI
