Back to Tamagui

πŸ“‹ Tamagui Subscription Documentation

code/tamagui.dev/doc/subcriptions.md

1.144.413.7 KB
Original Source

πŸ“‹ Tamagui Subscription Documentation

Overview

This document provides a comprehensive guide to the Tamagui subscription system, covering all subscription plans, database architecture, payment flows, and implementation details.

1. πŸ’³ Current Subscription Plans

πŸš€ PRO Plan

Database Tables: subscriptions, subscription_items, products, prices

  • πŸ”„ Recurring Subscription (Annual): $240/year
    • πŸ” Access to private Takeout GitHub repository
    • 🍱 Bento components download + private Bento source repo access
    • πŸ’¬ Private community Discord chat room (#takeout-general)
    • ♾️ Lifetime rights to all code and assets (even after subscription expires)
    • πŸ‘₯ Supports team seats addition
  • πŸ’° One-Time Payment: $400 (one year access)
    • βœ… Same benefits as recurring except:
    • ❌ No Discord takeout channel access (limitation by design)
    • ❌ Cannot add team seats (team seats require recurring subscription)
    • 🏁 Auto renewal false
    • πŸ“„ Creates invoice record instead of subscription

πŸ‘₯ Team Seats

Database Tables: team_subscriptions, team_members, subscription_items

  • πŸ’΅ Price: $100/seat/year (only available with PRO recurring)
  • 🎁 Benefits per seat:
    • ✨ Full PRO plan access for team member
    • πŸ’¬ Access to Discord #takeout-general channel
    • 🀝 Repository collaboration invite
  • ⚠️ Limitations:
    • πŸ‘‘ Only team owner can manage Discord access
    • 🚫 Cannot be purchased with one-time PRO plan
    • πŸ”— Linked to main PRO subscription via subscription_items

πŸ’¬ Chat Support

Database Tables: subscriptions, discord_invites

  • πŸ’΅ Price: $200/month
  • 🎁 Benefits:
    • πŸ”’ Private Discord room for your team
    • πŸ‘₯ 2 Discord invites included
    • ⚑ Prioritized responses over community chat
    • πŸ”§ Can be combined with any other plan
  • βš™οΈ Implementation: Creates separate monthly subscription

🎯 Support Tiers (1-3)

Database Tables: subscriptions, subscription_items

  • πŸ₯‰ Tier 1: $800/month
  • πŸ₯ˆ Tier 2: $1,600/month
  • πŸ₯‡ Tier 3: $2,400/month
  • 🎁 Benefits per tier:
    • ⏰ 4 hours of development time per month
    • πŸš€ Faster response times
    • πŸ‘₯ 4 additional private Discord chat invites
    • πŸ”§ Can be combined with Chat Support
  • βš™οΈ Implementation: Creates separate monthly subscription

2. πŸ”§ Detailed Implementation Flow

πŸš€ PRO Plan Implementation

πŸ”„ Recurring Subscription Flow

User Purchase β†’ create-subscription+api.ts β†’ Stripe Subscription Creation

πŸ—„οΈ Database Flow:

  1. πŸ”Œ API: create-subscription+api.ts
  2. πŸ’³ Stripe: Creates subscription with PRO_SUBSCRIPTION_PRICE_ID
  3. πŸͺ Webhook: webhook+api.ts handles customer.subscription.created
  4. πŸ—„οΈ Database:
    • βž• Insert into subscriptions table
    • βž• Insert into subscription_items table
    • πŸ”— Link via subscription_items.subscription_id

πŸ”‘ Key Code Points:

typescript
// create-subscription+api.ts
const subscription = await stripe.subscriptions.create({
  customer: stripeCustomerId,
  items: [{ price: PRO_SUBSCRIPTION_PRICE_ID }],
  payment_behavior: 'default_incomplete',
  expand: ['latest_invoice.payment_intent'],
})

πŸ’° One-Time Payment Flow

User Purchase β†’ create-subscription+api.ts β†’ Stripe Invoice Creation

πŸ—„οΈ Database Flow:

  1. πŸ”Œ API: create-subscription+api.ts (when disableAutoRenew: true)
  2. πŸ’³ Stripe: Creates invoice with PRO_ONE_TIME_PRICE_ID
  3. πŸͺ Webhook: webhook+api.ts handles invoice.paid
  4. πŸ—„οΈ Database:
    • βž• Insert into subscriptions with cancel_at_period_end: true
    • πŸ“… Set cancel_at to one year from creation
    • βž• Insert into subscription_items

πŸ”‘ Key Code Points:

typescript
// webhook+api.ts - manageOneTimePayment()
await supabaseAdmin.from('subscriptions').insert({
  id: invoice.id,
  user_id: uuid,
  status: 'active',
  cancel_at: oneYearFromNow.toISOString(),
  cancel_at_period_end: true,
})

πŸ‘₯ Team Seats Implementation

πŸ›’ Purchase Flow

User Purchase β†’ create-subscription+api.ts β†’ Update Existing PRO Subscription

πŸ—„οΈ Database Flow:

  1. πŸ”Œ API: create-subscription+api.ts (with teamSeats > 0)
  2. πŸ’³ Stripe: Adds TEAM_SEATS_SUBSCRIPTION_PRICE_ID to existing subscription
  3. πŸͺ Webhook: webhook+api.ts handles customer.subscription.updated
  4. πŸ—„οΈ Database:
    • πŸ”„ Update subscription_items table with team seats item
    • βž• Create team_subscriptions record via createTeamSubscription()
    • πŸ“Š Track seats in team_subscriptions.total_seats

πŸ”‘ Key Code Points:

typescript
// create-subscription+api.ts
let items: Stripe.SubscriptionCreateParams.Item[] = [{ price: PRO_SUBSCRIPTION_PRICE_ID }]
if (teamSeatCount > 0) {
  items.push({ price: TEAM_SEATS_SUBSCRIPTION_PRICE_ID, quantity: teamSeatCount })
}

πŸ‘€ Team Member Management

πŸ—„οΈ Database Tables: team_members, team_subscriptions, users

βž• Add Member Flow:

  1. πŸ”Œ API: team-seat+api.ts POST endpoint
  2. πŸ—„οΈ Database: Insert into team_members table
  3. πŸ™ GitHub: Invite to repository via resend-github-invite+api.ts
  4. πŸ’¬ Discord: Manual invitation through Discord panel

βž– Remove Member Flow:

  1. πŸ”Œ API: team-seat+api.ts DELETE endpoint
  2. πŸ—„οΈ Database: Delete from team_members table
  3. πŸ™ GitHub: Remove repository access
  4. πŸ’¬ Discord: Manual removal through Discord panel

πŸ’¬ Discord Seats Calculation

πŸ“ Logic Location: ensureSubscription.ts, Discord API endpoints

typescript
// Discord seats calculation logic
const baseSeats = subscription.quantity || 1 // PRO plan base seats
const teamSeats = teamSubscription?.total_seats || 0
const totalDiscordSeats = baseSeats + teamSeats

πŸ”„ Legacy Support: For old takeout prices, seats are calculated from price description parsing.

πŸ’¬ Chat Support & 🎯 Support Tiers Implementation

πŸ›’ Purchase Flow

User Purchase β†’ upgrade-subscription+api.ts β†’ Separate Monthly Subscription

πŸ—„οΈ Database Flow:

  1. πŸ”Œ API: upgrade-subscription+api.ts
  2. πŸ’³ Stripe: Creates separate monthly subscription
  3. πŸͺ Webhook: webhook+api.ts handles subscription events
  4. πŸ—„οΈ Database:
    • βž• Insert separate record in subscriptions table
    • πŸ“… Different billing cycle from PRO plan

πŸ”‘ Key Code Points:

typescript
// upgrade-subscription+api.ts
const items: Array<{ price: string; quantity?: number }> = []
if (chatSupport) {
  items.push({ price: STRIPE_PRODUCTS.CHAT.priceId })
}
if (supportTier > 0) {
  items.push({ price: STRIPE_PRODUCTS.SUPPORT.priceId, quantity: supportTier })
}

πŸ’¬ Discord Integration

πŸ—„οΈ Database Tables: subscriptions (metadata), discord_invites

πŸ—οΈ Channel Creation:

  • 🌐 General Channel: For PRO users (#takeout-general)
  • πŸ”’ Support Channel: For Chat/Support tier users (private)

πŸ“Š Metadata Storage:

  • Discord channel IDs stored in subscriptions table metadata:
json
{
  "discord_channel": "1132001717215559691"
}

πŸ‘₯ Member Management:

  • discord_invites table stores channel members and invitation status
  • Tracks which users have been invited to which Discord channels
  • Prevents duplicate invitations

πŸ”„ Reset Functionality:

  • πŸ”΄ UI Reset Button: Deletes entire Discord channel
  • πŸ”Œ API: discord/support+api.ts and discord/channel+api.ts DELETE endpoints
  • ⚠️ Effect: All members lose access, channel must be recreated

3. πŸ•°οΈ Legacy Products & Migration

🏷️ Product Ownership System

πŸ—„οΈ Database Table: product_ownership

🎯 Purpose:

  • πŸ“Š Track one-time purchases before subscription system
  • 🍱 Provide Bento access for legacy users
  • πŸ”§ Handle data migration issues

πŸ’» Usage:

typescript
// Check legacy 🍱 Bento access
const { data: ownership } = await supabase
  .from('product_ownership')
  .select('*')
  .eq('user_id', userId)
  .eq('product_id', BENTO_PRODUCT_ID)

πŸ•°οΈ Legacy Discord Seats Calculation

πŸ“ Location: ensureSubscription.ts

For old takeout subscriptions, Discord seats are calculated by parsing the price description:

typescript
// Legacy price description parsing
const description = price.description || ''
const seatsMatch = description.match(/(\d+)\s*seats?/i)
const seats = seatsMatch ? parseInt(seatsMatch[1]) : 1

πŸ”„ Migration Scripts

🎯 Purpose: Add users to product_ownership for data recovery

πŸ’» Usage: When users lose access after system migration, manually add records to restore their benefits.

4. πŸ™ Repository Access System

πŸ™ GitHub Integration

πŸ—„οΈ Database Table: claims

πŸ”„ Flow:

  1. πŸ‘† User Action: Click "Takeout 1" or "Takeout 2" to open repos directly, or "Resend Invite" to send/resend GitHub team invite
  2. πŸ”Œ API: resend-github-invite+api.ts handles invite requests
  3. πŸ” GitHub Check: checkIfUserIsTeamMember() in github/helpers.ts
  4. πŸ“€ Response Handling:
    • βœ… Already Member: Returns success message
    • πŸ“§ New Invitation: Sends GitHub team invite, shows success message

5. πŸ—„οΈ Database Schema Summary

πŸ—οΈ Core Tables

  • subscriptions: Main subscription records
  • subscription_items: Links subscriptions to products/prices
  • products: Product definitions (PRO, Team Seats, Chat, Support)
  • prices: Pricing information for each product
  • customers: Stripe customer ID mapping

πŸ‘₯ Team Management

  • team_subscriptions: Team subscription metadata
  • team_members: Team member relationships
  • team_invoices: Team-specific invoice tracking

πŸ” Access Control

  • claims: Repository and service access claims
  • product_ownership: Legacy one-time purchase tracking
  • users_private: GitHub tokens and private data

πŸ’¬ Discord Integration

  • subscriptions: Discord channel metadata (stores channel IDs in JSON)
  • discord_invites: Discord member invitations and invitation status

6. ⚠️ Current Issues & TODOs

πŸ› Known Issues

  1. πŸ”€ Multiple Support Subscriptions Complexity

    • Users can have both Chat and Support tier subscriptions
    • Creates billing complexity and multiple Discord channels
    • Seat calculation becomes complex across multiple subscriptions
  2. πŸ”„ Discord Reset Limitations

    • Reset button deletes entire channel
    • Hard to add new members after reset
    • No granular member removal
  3. πŸ’° One-Time Payment Limitations

    • No Discord access for one-time PRO purchases
    • Cannot add team seats to one-time purchases
  4. πŸ’³ Stripe Payment Confirmation Issue with Large Discounts

    • Problem: When invoices are automatically paid due to large discounts (below Stripe's $0.50 minimum), Stripe doesn't create a payment intent, so there's no clientSecret to use for stripe.confirmPayment()
    • Solution: Check if the subscription/invoice is already paid and skip the payment confirmation step
    • Implementation: Use data.amount_due && data.amount_due > 0 && data.clientSecret condition before calling stripe.confirmPayment()
    • Affected Scenarios: Heavy discount codes (99.9% off) that bring total below $0.50 USD

πŸš€ Planned Improvements

  1. πŸ”— Consolidate Chat + Support Subscriptions

    typescript
    // TODO: When user has Chat and purchases Support tier,
    // upgrade existing Chat subscription instead of creating new one
    // Benefits: Single Discord channel, unified billing, easier management
    
  2. ✨ Enhanced Discord Management

    • Individual member removal
    • Bulk member operations
    • Channel recreation without full reset
  3. πŸ“Š Improved Team Seat Management

    • Better seat utilization tracking
    • Automated seat cleanup for inactive members

7. πŸ§ͺ Testing & Development

🎭 User Impersonation Tool

For testing user access and subscriptions, use the Supabase SSR User Impersonate Tool:

bash
npm install
cp .env.example .env  # Fill in Supabase credentials
node main.mjs --email <user-email>

This tool allows developers to impersonate any user for testing subscription flows, Discord access, and repository claims.

🎯 Key Testing Scenarios

  1. πŸš€ PRO Subscription: Test both recurring and one-time flows
  2. πŸ‘₯ Team Seats: Test member addition/removal and Discord access
  3. 🎯 Support Tiers: Test private channel creation and seat calculation
  4. πŸ™ Repository Claims: Test GitHub collaboration invites
  5. πŸ”„ Legacy Migration: Test product ownership access

8. πŸ”Œ API Endpoints Summary

πŸ’³ Subscription Management

  • /api/create-subscription: Create PRO + Team Seats subscriptions
  • /api/upgrade-subscription: Add Chat/Support tier subscriptions
  • /api/add-team-seats: Add additional team seats to existing subscription
  • /api/cancel-subscription: Cancel subscription with period end

πŸ‘₯ Team Management

  • /api/team-seat: GET (list), POST (invite), DELETE (remove) team members

πŸ’¬ Discord Integration

  • /api/discord/channel: Manage general Discord channel access
  • /api/discord/support: Manage private support channel access
  • /api/discord/search-member: Search Discord members for invitations

πŸͺ Webhooks

  • /api/stripe/webhook: Handle all Stripe webhook events for subscription lifecycle

This documentation provides a complete overview of the Tamagui subscription system architecture and implementation details for team members to understand and maintain the codebase effectively.