D
Docs
Documentation Organization Subscriptions

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 processed
  • incomplete_expired - Payment failed and expired
  • trialing - In trial period
  • active - Active and paid
  • past_due - Payment failed but still active
  • canceled - Subscription canceled
  • unpaid - 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 status
  • plan - Filter by plan name
  • stripeCustomerId - 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

  1. User selects plan on your pricing page
  2. System creates checkout session with organization context
  3. User completes payment on Stripe checkout
  4. Webhook processes completion and creates/updates subscription
  5. 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 details
  • subscription:write - Create and update subscriptions
  • subscription: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 activated
  • customer.subscription.updated - Subscription modified (plan change, etc.)
  • customer.subscription.deleted - Subscription canceled
  • invoice.payment_succeeded - Successful payment
  • invoice.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

  1. Always check permissions before subscription operations
  2. Use organization context for all subscription operations
  3. Handle webhook events asynchronously and idempotently
  4. Provide clear error messages for limit violations
  5. Implement graceful degradation when subscriptions expire

User Experience

  1. Show current plan status prominently in the UI
  2. Provide upgrade prompts when limits are approached
  3. Make billing portal easily accessible
  4. Display trial information clearly
  5. Handle subscription changes with appropriate notifications

Error Handling

  1. Validate organization access before operations
  2. Handle Stripe API errors gracefully
  3. Provide meaningful error messages to users
  4. Log subscription events for debugging
  5. 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.