This guide will help you set up Stripe subscriptions for QA Studio's team functionality.
QA Studio uses Stripe for:
- Team subscription management (Pro plan)
- Per-seat billing
- Self-service billing portal
- Automatic seat limit enforcement
- Free Individual Users: Anyone can sign up and use QA Studio for free (1 user only)
- Team Creation: Users can create teams
- Team Subscription: To add members to a team, the team must have a Pro subscription
- Seat Management: Subscriptions include a certain number of seats. Adding more members requires more seats.
model Team {
subscription Subscription?
members User[]
}
model Subscription {
teamId String
stripeCustomerId String
stripeSubscriptionId String
stripePriceId String
status SubscriptionStatus
seats Int
currentPeriodEnd DateTime
}- Go to stripe.com and create an account
- Activate your account (for production)
- Get your API keys from the Stripe Dashboard
-
Go to Products → Add Product
-
Create the Pro plan:
- Name: QA Studio Pro
- Description: Team collaboration with AI-powered features
- Pricing Model: Recurring
-
Add pricing options:
Monthly Price:
- Price: $10/month per seat
- Billing period: Monthly
- Usage type: Licensed (per seat)
- Copy the Price ID (starts with
price_)
Yearly Price:
- Price: $100/year per seat
- Billing period: Yearly
- Usage type: Licensed (per seat)
- Copy the Price ID (starts with
price_)
Update your .env file with the following:
# Stripe Secret Key (from Stripe Dashboard → Developers → API Keys)
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Stripe Webhook Secret (we'll get this in Step 4)
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Public variables
PUBLIC_BASE_URL=http://localhost:5173
PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PUBLIC_STRIPE_PRICE_ID_PRO_MONTHLY=price_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PUBLIC_STRIPE_PRICE_ID_PRO_YEARLY=price_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxWebhooks keep your database in sync with Stripe subscription events.
-
Install Stripe CLI: https://stripe.com/docs/stripe-cli
-
Login to Stripe:
stripe login
-
Forward webhooks to your local server:
stripe listen --forward-to localhost:5173/api/webhooks/stripe
-
Copy the webhook signing secret (starts with
whsec_) and add to.env:STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-
Go to Stripe Dashboard → Developers → Webhooks
-
Click Add Endpoint
-
Endpoint URL:
https://your-domain.com/api/webhooks/stripe -
Events to listen for:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
-
Copy the Signing Secret and add to your production environment variables
The Customer Portal allows users to manage their subscriptions, update payment methods, and view invoices.
-
Go to Settings → Billing → Customer Portal
-
Enable the portal
-
Configure settings:
- ✅ Allow customers to update payment methods
- ✅ Allow customers to update billing information
- ✅ Allow customers to view invoices
- ✅ Allow customers to cancel subscriptions
- ✅ Allow customers to update subscription quantities (seats)
-
Start your dev server:
npm run dev
-
Start Stripe CLI webhook forwarding:
stripe listen --forward-to localhost:5173/api/webhooks/stripe
-
Sign in to your app
-
Navigate to
/teams/new -
Create a team with the Pro plan
-
Use Stripe test card:
4242 4242 4242 4242- Any future expiry date
- Any 3-digit CVC
- Any ZIP code
Check your terminal running stripe listen to see webhook events:
- ✅
checkout.session.completed - ✅
customer.subscription.created - ✅
invoice.payment_succeeded
Check your database:
npx prisma studioVerify:
Teamrecord createdSubscriptionrecord created withstatus = 'ACTIVE'Userrecord hasteamIdpopulated
-
Go to your team page:
/teams/[teamId] -
Click Manage Billing
-
Verify you can:
- Update payment method
- Change subscription quantity (seats)
- View invoices
- Cancel subscription
import { requireActiveSubscription, requireFeature } from '$lib/server/subscriptions';
export const POST: RequestHandler = async ({ locals, params }) => {
const userId = await requireAuth(locals);
// Require active subscription
await requireActiveSubscription(params.teamId);
// Require specific feature
await requireFeature(params.teamId, 'ai_analysis');
// Your logic here...
};import { requireAvailableSeats } from '$lib/server/subscriptions';
export const POST: RequestHandler = async ({ request, params }) => {
// Check if team has available seats before adding member
await requireAvailableSeats(params.teamId);
// Add member...
};import { getTeamLimits } from '$lib/server/subscriptions';
const limits = await getTeamLimits(teamId);
console.log(limits);
// {
// plan: 'pro',
// seats: { max: 5, used: 3, available: 2 },
// features: { ai_analysis: true, advanced_reports: true },
// subscription: { status: 'ACTIVE', currentPeriodEnd: Date }
// }- 1 user only (individual account)
- Unlimited projects
- Basic test management
- Community support
- Up to 10+ team members (per seat)
- Unlimited projects
- Advanced test management
- ✨ AI-powered failure analysis
- ✨ Advanced reporting
- ✨ Custom integrations
- Priority support
Gate premium features in your code:
import { isFeatureAvailable } from '$lib/server/subscriptions';
export const load: PageServerLoad = async ({ params }) => {
const team = await getTeam(params.teamId);
const hasAI = await isFeatureAvailable(team.id, 'ai_analysis');
return {
team,
features: {
aiAnalysis: hasAI
}
};
};In your UI:
{#if features.aiAnalysis}
<AIAnalysisPanel />
{:else}
<UpgradePrompt feature="AI-powered failure analysis" />
{/if}- Verify webhook endpoint URL is correct
- Check webhook signing secret matches
.env - Ensure endpoint is publicly accessible (for production)
- Check Stripe Dashboard → Webhooks → Recent Deliveries for errors
- Check webhook is receiving events
- Verify
teamIdis in subscription metadata - Check database for subscription record
- Look for errors in webhook logs
- Verify price IDs are correct
- Check Stripe publishable key is correct
- Ensure customer email is valid
- Check success/cancel URLs are accessible
-
Webhook Signature Verification: Always verify webhook signatures (already implemented)
-
Environment Variables: Never commit
.envfiles to git -
Role-Based Access: Only ADMIN and MANAGER roles can manage billing
-
Seat Enforcement: Automatically enforced before adding members
-
Test vs Production Keys: Use test keys in development, production keys in production
Before launching to production:
- ✅ Activate your Stripe account
- ✅ Switch to production API keys
- ✅ Create production webhook endpoint
- ✅ Update production environment variables
- ✅ Test with real payment method (then refund)
- ✅ Set up tax collection (if required)
- ✅ Configure email receipts in Stripe