Organization Subscriptions
In the SprintKit, subscriptions are scoped to organizations, enabling multi-tenant billing where each organization can have its own subscription plan and billing relationship.
Overview
Each organization can have:
- One active subscription at a time
- Independent billing from other organizations
- Plan-specific features and limits
- Trial periods and promotional pricing
- Subscription management permissions based on organization roles
Subscription Structure
Subscription Properties
interface Subscription {
id: string // Unique subscription identifier
plan: string // Plan name (references subscription plan)
referenceId: string // Organization ID
stripeCustomerId?: string // Stripe customer ID
stripeSubscriptionId?: string // Stripe subscription ID
status: string // Subscription status
periodStart?: Date // Current period start
periodEnd?: Date // Current period end
cancelAtPeriodEnd?: boolean // Cancel at period end flag
seats?: number // Number of seats (for seat-based plans)
trialStart?: Date // Trial period start
trialEnd?: Date // Trial period end
billingInterval?: 'monthly' | 'annual' // Billing frequency
}
Subscription Status
Subscriptions can have the following statuses:
incomplete
- Payment not yet processedincomplete_expired
- Payment failed and expiredtrialing
- In trial periodactive
- Active and paidpast_due
- Payment failed but still activecanceled
- Subscription canceledunpaid
- Payment failed and subscription suspended
Managing Organization Subscriptions
Get Organization Subscriptions
Retrieve all subscriptions for an organization:
GET /api/organizations/{organizationId}/subscriptions
Query Parameters:
status
- Filter by subscription statusplan
- Filter by plan namestripeCustomerId
- Filter by Stripe customer ID
Response:
{
"status": "success",
"data": [
{
"id": "sub_123",
"plan": "pro",
"referenceId": "org_456",
"stripeCustomerId": "cus_stripe123",
"stripeSubscriptionId": "sub_stripe456",
"status": "active",
"periodStart": "2024-01-01T00:00:00Z",
"periodEnd": "2024-02-01T00:00:00Z",
"cancelAtPeriodEnd": false,
"seats": 10,
"trialStart": null,
"trialEnd": null,
"billingInterval": "monthly",
"planDetails": {
"id": "plan_pro",
"name": "pro",
"displayName": "Pro Plan",
"features": ["Advanced analytics", "Priority support"],
"limits": {
"seats": 25,
"storage": 100,
"apiCalls": 10000
}
}
}
]
}
Create Organization Subscription
Create a new subscription for an organization:
POST /api/organizations/{organizationId}/subscriptions
Request Body:
{
"plan": "pro",
"referenceId": "org_456",
"status": "trialing",
"seats": 10,
"trialStart": "2024-01-01T00:00:00Z",
"trialEnd": "2024-01-15T00:00:00Z",
"billingInterval": "monthly"
}
Update Subscription
Update an existing subscription:
PUT /api/organizations/{organizationId}/subscriptions/{subscriptionId}
Request Body:
{
"plan": "enterprise",
"seats": 25,
"billingInterval": "annual"
}
Cancel Subscription
Cancel a subscription:
DELETE /api/organizations/{organizationId}/subscriptions/{subscriptionId}
Checkout Process
Create Checkout Session
Initiate a Stripe checkout session for subscription purchase:
POST /api/subscriptions/checkout
Request Body:
{
"planId": "plan_pro_123",
"billingInterval": "monthly",
"organizationId": "org_456",
"successUrl": "https://yourapp.com/billing/success",
"cancelUrl": "https://yourapp.com/billing/cancel",
"seats": 10
}
Response:
{
"status": "success",
"data": {
"sessionId": "cs_stripe_session_123",
"url": "https://checkout.stripe.com/pay/cs_stripe_session_123"
}
}
Checkout Flow
- User selects plan on your pricing page
- System creates checkout session with organization context
- User completes payment on Stripe checkout
- Webhook processes completion and creates/updates subscription
- User is redirected to success page with active subscription
Billing Portal
Access Billing Portal
Redirect users to Stripe's customer portal for self-service billing:
POST /api/subscriptions/billing-portal
Request Body:
{
"organizationId": "org_456",
"returnUrl": "https://yourapp.com/billing"
}
Response:
{
"status": "success",
"data": {
"url": "https://billing.stripe.com/session/xyz123"
}
}
The billing portal allows users to:
- Update payment methods
- Download invoices
- View billing history
- Cancel subscriptions
- Update billing information
Permission System
Access Control
Subscription operations require appropriate permissions:
- Organization owners have full subscription access
- Members with subscription permissions can view/manage based on role
- Permission checks are enforced at the API level
Permission Types
subscription:read
- View subscription detailssubscription:write
- Create and update subscriptionssubscription:delete
- Cancel subscriptions
Checking Permissions
The system automatically checks permissions using:
const hasAccess = await userHasSubscriptionAccess(
userId,
organizationId,
'write' // 'read' | 'write' | 'delete'
)
Usage Tracking and Limits
Plan Limits Enforcement
Organizations are subject to their subscription plan limits:
// Example: Check if organization can add more seats
const subscription = await getOrganizationSubscription(organizationId)
const planLimits = subscription.planDetails.limits
if (planLimits.seats && currentSeats >= planLimits.seats) {
throw new Error('Seat limit exceeded. Please upgrade your plan.')
}
Common Limit Checks
- Seat limits - Maximum team members
- Storage limits - File storage quotas
- API limits - Monthly API call quotas
- Feature limits - Access to premium features
Subscription Lifecycle Events
Webhook Processing
The system handles Stripe webhooks to manage subscription lifecycle:
customer.subscription.created
- New subscription activatedcustomer.subscription.updated
- Subscription modified (plan change, etc.)customer.subscription.deleted
- Subscription canceledinvoice.payment_succeeded
- Successful paymentinvoice.payment_failed
- Failed payment
Status Transitions
graph TD
A[incomplete] --> B[trialing]
A --> C[active]
B --> C[active]
C --> D[past_due]
C --> E[canceled]
D --> C[active]
D --> F[unpaid]
F --> G[canceled]
Best Practices
Subscription Management
- Always check permissions before subscription operations
- Use organization context for all subscription operations
- Handle webhook events asynchronously and idempotently
- Provide clear error messages for limit violations
- Implement graceful degradation when subscriptions expire
User Experience
- Show current plan status prominently in the UI
- Provide upgrade prompts when limits are approached
- Make billing portal easily accessible
- Display trial information clearly
- Handle subscription changes with appropriate notifications
Error Handling
- Validate organization access before operations
- Handle Stripe API errors gracefully
- Provide meaningful error messages to users
- Log subscription events for debugging
- Implement retry logic for webhook processing
Integration Examples
Frontend Integration
<template>
<div class="subscription-status">
<h3>Current Plan: {{ subscription.planDetails.displayName }}</h3>
<p>Status: {{ subscription.status }}</p>
<p>Billing: {{ subscription.billingInterval }}</p>
<div v-if="subscription.trialEnd">
Trial ends: {{ formatDate(subscription.trialEnd) }}
</div>
<button @click="openBillingPortal">
Manage Billing
</button>
</div>
</template>
<script setup>
const { data: subscription } = await $fetch(`/api/organizations/${organizationId}/subscriptions`)
const openBillingPortal = async () => {
const { data } = await $fetch('/api/subscriptions/billing-portal', {
method: 'POST',
body: { organizationId }
})
window.location.href = data.url
}
</script>
Backend Service Integration
// Check subscription limits before allowing action
export async function createProject(organizationId: string, projectData: any) {
const subscription = await getOrganizationSubscription(organizationId)
if (subscription.planDetails.limits.projects) {
const currentProjects = await getProjectCount(organizationId)
if (currentProjects >= subscription.planDetails.limits.projects) {
throw new Error('Project limit reached. Please upgrade your plan.')
}
}
return await createProjectInDatabase(projectData)
}
This organization-scoped subscription system provides the foundation for a robust multi-tenant SaaS billing solution.