Per-seat billing is a common SaaS pricing model that charges customers based on the number of active users in their organization. Dopamine Starter Kit provides automated per-seat billing that dynamically adjusts subscription costs based on workspace membership.
Overview
What is Per-Seat Billing?
Per-seat billing, also known as per-user pricing, charges customers for each active user in their workspace. This model provides:
- Scalable pricing that grows with customer usage
- Transparent costs directly tied to value delivered
- Automatic billing adjustments when teams grow or shrink
- Fair pricing for teams of all sizes
Key Benefits
- Automatic seat calculation: No manual intervention required
- Real-time updates: Billing adjusts immediately when membership changes
- Prorated charges: Fair billing for mid-cycle changes
- Usage transparency: Clear visibility into seat usage and costs
Architecture
Per-Seat Billing Flow
graph LR
A[User Joins<br/>Workspace] --> B[Count Paid<br/>Members]
B --> C[Update Subscription<br/>Quantity]
C --> D[Stripe Processes<br/>Proration]
D --> E[Webhook Updates<br/>Database]
E --> F[Billing Reflects<br/>New Seat Count]
Core Components
- Workspace Membership: Source of truth for active users
- Seat Calculation: Automatic counting of billable members
- Stripe Integration: Quantity-based subscription management
- Webhook Handling: Real-time subscription updates
- Database Synchronization: Consistent state across systems
Seat Calculation
Counting Paid Members
The system automatically calculates seats based on workspace membership.
Seat Calculation Rules
- Minimum Seat Count: Every workspace has at least 1 seat (owner)
- Real-time Counting: Seat count updates immediately on membership changes
- Owner Inclusion: Workspace owner is always counted as 1 seat
Subscription Management
Checkout with Per-Seat Pricing
When creating a subscription, the system automatically calculates the required seats:
// apps/api/src/billing/stripe.service.ts
async createCheckoutSession({
user,
workspace,
plan,
interval = 'monthly',
}) {
const priceId = this.plans[plan][interval];
// Automatic seat calculation
const seats = await this.workspaceService.countPaidMembers(workspace.id);
return await this.stripe.checkout.sessions.create({
line_items: [{
price: priceId,
quantity: seats, // Per-seat quantity
}],
mode: 'subscription',
// ... other options
});
}
Pricing Configuration
Stripe Price Setup
Configure per-seat prices in Stripe Dashboard:
- Create Product: Define your SaaS product
- Set Pricing Model: Choose "Per unit" pricing
- Configure Billing: Set monthly/yearly intervals
- Enable Prorations: Allow mid-cycle adjustments
Price IDs Configuration
# apps/api/.env
STRIPE_PRO_MONTHLY_PRICE_ID=price_1234567890_pro_monthly
STRIPE_PRO_YEARLY_PRICE_ID=price_1234567890_pro_yearly
STRIPE_BUSINESS_MONTHLY_PRICE_ID=price_1234567890_business_monthly
STRIPE_BUSINESS_YEARLY_PRICE_ID=price_1234567890_business_yearly
Webhook Handling
Subscription Updates
Handle quantity changes through Stripe webhooks:
private async handleSubscriptionUpdated(subscription: Stripe.Subscription) {
const item = subscription.items.data[0];
const newQuantity = item.quantity;
await this.subscriptionRepository.updateMany({
where: { stripeSubscriptionId: subscription.id },
data: {
quantity: newQuantity,
currentPeriodStart: new Date(item.current_period_start * 1000),
currentPeriodEnd: new Date(item.current_period_end * 1000),
},
});
}
Prorated Billing Events
Stripe automatically handles prorations for mid-cycle changes:
- Seat additions: Immediate charge for remaining period
- Seat removals: Credit applied to next invoice
- Plan changes: Prorated upgrade/downgrade handling
Billing Scenarios
New Workspace Creation
- User creates workspace → Counted as 1 seat
- Checkout session created with quantity: 1
- Subscription activated with 1 seat billing
Adding Team Members
- Invitation sent to new member
- Member accepts and joins workspace
- Seat count increases: countPaidMembers() → 2
- Subscription quantity updated in Stripe
- Prorated charge applied immediately
Removing Team Members
- Member removed from workspace
- Seat count decreases: countPaidMembers() → 1
- Subscription quantity updated in Stripe
- Credit applied to next billing cycle
Plan Upgrades
- User selects higher tier plan
- New checkout session with current seat count
- Existing subscription cancelled
- New subscription created with same quantity
- Prorated billing for plan difference
Conclusion
Per-seat billing in Dopamine provides a scalable, transparent, and automated billing solution that grows with your customers' teams while maintaining accuracy and fairness in pricing.