ShopySeed

Billing & Plans

Configure Stripe billing, subscription plans, plan limits, and handle upgrades and downgrades in ShopySeed.

Billing & Plans

ShopySeed includes a complete Stripe billing integration with subscription management, checkout, customer portal, and webhook handling.

Plans Configuration

Plans are defined in apps/api/src/billing/config/plans.ts:

export const PLANS: Plans = {
  free: {
    name: 'Free',
    stripePriceId: null,
    limits: { members: 3, storage: '100MB' },
    features: ['Up to 3 members', '100MB storage', 'Basic support'],
  },
  pro: {
    name: 'Pro',
    stripePriceId: null, // Loaded from STRIPE_PRO_PRICE_ID env var
    price: 29,
    limits: { members: 10, storage: '10GB' },
    features: ['Up to 10 members', '10GB storage', 'Priority support', 'API access'],
  },
  enterprise: {
    name: 'Enterprise',
    stripePriceId: null, // Loaded from STRIPE_ENTERPRISE_PRICE_ID env var
    price: 99,
    limits: { members: -1, storage: '100GB' }, // -1 = unlimited
    features: ['Unlimited members', '100GB storage', 'Dedicated support', 'API access', 'SSO', 'Custom integrations'],
  },
};

Customizing Plans

To modify plans, edit the PLANS object. Each plan needs:

  • name: Display name
  • stripePriceId: Stripe Price ID (set via env var for paid plans)
  • price: Monthly price in USD (null for free)
  • limits: Resource limits (members: -1 for unlimited)
  • features: Feature list displayed on the pricing page

Stripe Setup

1. Create Products & Prices in Stripe

In your Stripe Dashboard:

  1. Create a product for each paid plan (Pro, Enterprise)
  2. Add a recurring price to each product
  3. Copy the Price IDs

2. Configure Environment Variables

STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRO_PRICE_ID=price_...
STRIPE_ENTERPRISE_PRICE_ID=price_...
FRONTEND_URL=http://localhost:3010

3. Set Up Webhooks

Create a webhook endpoint in Stripe pointing to: {API_URL}/billing/webhooks

Subscribe to these events:

  • checkout.session.completed
  • invoice.paid
  • invoice.payment_failed
  • customer.subscription.updated
  • customer.subscription.deleted

For local development, use the Stripe CLI:

stripe listen --forward-to localhost:3011/api/billing/webhooks

Subscription Flow

Upgrade (Free → Pro/Enterprise)

  1. User clicks "Choose This Plan" on the pricing page or "Upgrade" in billing settings
  2. Backend creates a Stripe Checkout Session with the plan's price ID
  3. User completes payment on Stripe's hosted checkout page
  4. Stripe fires checkout.session.completed webhook
  5. Backend updates the subscription in the database via syncSubscription()

Downgrade

Downgrades are handled through the Stripe Customer Portal:

  1. User clicks "Manage Billing" in dashboard
  2. Backend creates a Stripe Billing Portal session
  3. User changes their plan in the portal
  4. Stripe fires customer.subscription.updated webhook
  5. Backend syncs the new plan and limits

Downgrade behavior (Option B):

  • Existing members are not removed
  • If the org exceeds the new plan's member limit, new invitations are blocked
  • A warning banner appears on the Team page
  • The org works normally until they reduce members or upgrade

Cancellation

When a subscription is canceled:

  1. Stripe fires customer.subscription.deleted webhook
  2. Backend sets the plan back to free and status to CANCELED
  3. Member limits revert to free plan limits (3 members)

Plan Limits Enforcement

The LimitsService enforces plan limits:

  • Member invitations: Checked before sending an invitation and before accepting one
  • Guard-based: Use @CheckLimits({ type: 'members' }) decorator on any endpoint
  • Programmatic: Call limitsService.enforceMemberLimit(orgId) in service logic

API Endpoints

MethodEndpointDescription
GET/billing/plansGet available plans (public)
GET/billing/subscriptionGet current subscription
POST/billing/checkoutCreate Stripe checkout session
POST/billing/portalCreate Stripe billing portal session
POST/billing/webhooksStripe webhook handler

Pricing Page

The pricing page (/pricing) dynamically loads plans from the API. All plans display their price (€0/month for Free) with a consistent "Get Started" CTA that redirects to registration (or dashboard for logged-in users).

On this page