xtablo-source/docs/STRIPE_IMPLEMENTATION_SUMMARY.md
2025-11-10 08:53:03 +01:00

11 KiB

Stripe Integration Implementation Summary

Overview

Complete Stripe integration for Xtablo with a single "Standard" subscription plan.

Architecture:

  • Webhook-based: Stripe webhooks sync data to Supabase
  • Direct Supabase access: Frontend queries Supabase directly (no API for reads)
  • API for actions: Checkout and subscription management via Node.js API
  • RLS-protected: Row Level Security ensures data privacy

What Has Been Implemented

1. Database Schema (sql/35_stripe_wrappers.sql)

Tables (Synced via Webhooks)

  • public.stripe_customers - Customer records with user mapping
  • public.stripe_subscriptions - Subscription history
  • public.stripe_products - Product catalog
  • public.stripe_prices - Pricing information

Profile Enhancements

  • profiles.is_paying - Boolean flag for quick checks
  • profiles.subscription_tier - Current tier ('free' or 'standard')

Helper Functions

-- Check if user is paying
SELECT is_paying_user(auth.uid());

-- Get subscription details
SELECT * FROM get_user_subscription_status(auth.uid());

-- Get Stripe customer ID
SELECT get_user_stripe_customer_id(auth.uid());

Views

  • active_subscriptions - All active subs with user info

Automatic Updates

  • Triggers automatically update profiles.is_paying when subscriptions change
  • updated_at timestamps managed automatically

2. Webhook Handlers (sql/36_stripe_webhooks.sql)

Includes functions to process all major Stripe events:

Customer Events:

  • handle_stripe_customer_created()
  • handle_stripe_customer_updated()
  • handle_stripe_customer_deleted()

Product Events:

  • handle_stripe_product_upsert()
  • handle_stripe_product_deleted()

Price Events:

  • handle_stripe_price_upsert()
  • handle_stripe_price_deleted()

Subscription Events:

  • handle_stripe_subscription_upsert()
  • handle_stripe_subscription_deleted()

3. Backend API (api/src/stripe.ts & api/src/stripe-webhook.ts)

Endpoints:

  • POST /api/v1/stripe/webhook - Stripe webhook handler (signature verified)
  • POST /api/v1/stripe/create-checkout-session - Start subscription checkout
  • POST /api/v1/stripe/create-portal-session - Open customer portal
  • POST /api/v1/stripe/cancel-subscription - Cancel at period end
  • POST /api/v1/stripe/reactivate-subscription - Reactivate subscription

Note: Subscription status queries (is-paying, subscription, prices) are handled directly by the frontend using Supabase client with RLS policies.

Features:

  • Automatic customer creation with user mapping
  • Secure webhook signature verification
  • Complete subscription lifecycle management
  • Customer portal access
  • Subscription cancellation/reactivation
  • Optimistic updates (API updates DB before webhook for instant UI feedback)

4. Frontend Hooks (apps/main/src/hooks/stripe.ts)

Available Hooks:

Direct Supabase Queries (Fast, RLS-protected):

  • useSubscription() - Get subscription details from Supabase
  • useIsPayingUser() - Check if user is paying (from user profile)
  • useStripePrices() - Get available prices from Supabase

API Calls (For Actions):

  • useCreateCheckoutSession() - Start checkout (calls API)
  • useCreatePortalSession() - Open portal (calls API)
  • useCancelSubscription() - Cancel subscription (calls API)
  • useReactivateSubscription() - Reactivate subscription (calls API)

5. TypeScript Types (packages/shared/src/types/stripe.types.ts)

Complete type definitions:

  • StripeCustomer
  • StripeSubscription
  • StripeProduct
  • StripePrice
  • Helper functions for formatting and status checks

6. Documentation (docs/STRIPE_SETUP.md)

Comprehensive setup guide including:

  • Step-by-step configuration
  • Edge Function example
  • Frontend integration examples
  • Testing instructions
  • Troubleshooting tips

🎯 Key Features

Security

  • Row Level Security (RLS) on all tables
  • Users can only see their own data
  • Stripe API key stored securely in Vault
  • Webhook signature verification
  • Service role functions for webhooks

Performance

  • Indexed on user_id, customer_id, status
  • Efficient queries for subscription checks
  • Cached subscription status in profiles table

Data Integrity

  • Foreign key relationships
  • Cascading deletes
  • Automatic timestamp management
  • Transaction safety in webhook handlers

📊 How to Check if User is Paying

Method 1: From Profile (Fastest)

const user = useUser();
if (user.is_paying) {
  // User has active Standard subscription
  console.log(`User is on ${user.subscription_tier} plan`); // 'standard'
}

Method 2: Using Hook (Same as Profile)

const { data: isPaying } = useIsPayingUser();
// Returns user.is_paying directly

Method 3: Direct Supabase Query

const { data: subscription } = await supabase
  .from("stripe_subscriptions")
  .select("*")
  .eq("user_id", userId)
  .in("status", ["active", "trialing"])
  .single();

const isPaying = !!subscription;

🔄 Data Flow

Subscription Creation Flow

  1. User Clicks "Subscribe to Standard" → Frontend calls useCreateCheckoutSession({ priceId }) → Calls API: POST /api/v1/stripe/create-checkout-session

  2. API Creates Checkout → Creates/retrieves Stripe customer (with user_id in metadata) → Creates Stripe checkout session → Returns checkout URL → Frontend redirects to Stripe

  3. User Completes Payment → Stripe processes payment → Fires customer.subscription.created webhook

  4. Webhook Received → API endpoint POST /api/v1/stripe/webhook receives event → Verifies signature → Calls handle_stripe_subscription_upsert() database function

  5. Database Updated → Function inserts into stripe_subscriptions → Trigger fires: update_profile_on_subscription_change → Updates profiles.is_paying = true and subscription_tier = 'standard'

  6. Frontend Queries SupabaseuseSubscription() queries stripe_subscriptions table directly → useUser() shows updated is_paying and subscription_tier → UI automatically reflects new status

Read Flow (No API Needed)

Frontend → Supabase Client → RLS Policies → stripe_subscriptions table → User's data

Benefits:

  • Fast: Direct database access, no API hop
  • 🔒 Secure: RLS ensures users only see their data
  • 📊 Real-time: Can use Supabase realtime subscriptions if needed

🚀 Next Steps to Complete Implementation

Required Setup:

  1. Update Vault Key ID

    • Get key_id after storing Stripe API key
    • Update in sql/35_stripe_wrappers.sql line 27
  2. Install Stripe SDK

    cd apps/api
    npm install stripe @stripe/stripe-js
    
  3. Configure API Environment Variables Add to api/.env:

    STRIPE_SECRET_KEY=sk_test_xxxxx
    STRIPE_WEBHOOK_SECRET=whsec_xxxxx
    SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
    
  4. Configure Frontend Environment Add to apps/main/.env:

    VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
    
  5. Create "Standard" Plan in Stripe

    • Create product named exactly "Standard"
    • Add pricing (monthly/yearly)
    • Note price IDs for frontend
  6. Configure Webhook in Stripe Dashboard

    • Add endpoint: https://your-api.com/api/v1/stripe/webhook
    • Select all customer/subscription/product/price events
    • Copy webhook signing secret

Frontend Implementation:

  1. Hooks Already Created

    • useSubscription() - Get subscription details
    • useIsPayingUser() - Check payment status
    • useCreateCheckoutSession() - Initiate checkout
    • useCreatePortalSession() - Customer portal
    • useCancelSubscription() - Cancel subscription
    • useReactivateSubscription() - Reactivate
  2. Build UI Components

    • Pricing page
    • Subscription management page
    • Payment status badges
    • Upgrade prompts
  3. Add Feature Gates

    if (!user.is_paying) {
      return <PremiumFeatureLockedPrompt />;
    }
    

📝 Environment Variables Needed

API (api/.env)

# Stripe
STRIPE_SECRET_KEY=sk_test_xxxxx (or sk_live_xxxxx)
STRIPE_WEBHOOK_SECRET=whsec_xxxxx

# Supabase
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJxxx...

# Frontend URL (for redirects)
FRONTEND_URL=http://localhost:5173 (or https://app.xtablo.com)

Frontend (apps/main/.env)

# Stripe
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx (or pk_live_xxxxx)

🧪 Testing Checklist

  • Run database migrations (35 & 36)
  • Install Stripe SDK in API: npm install stripe @stripe/stripe-js
  • Install Stripe SDK in frontend: npm install @stripe/stripe-js
  • Configure environment variables (API + Frontend)
  • Create "Standard" product in Stripe Dashboard
  • Configure webhook endpoint in Stripe
  • Test webhook with Stripe CLI
  • Can create checkout session from frontend
  • Payment redirects back to app
  • Webhook syncs subscription to database
  • profiles.is_paying updates automatically
  • Users can only see their own subscription data
  • is_paying_user() function returns correct status
  • Can access customer portal
  • Subscription cancellation updates status
  • Test cards work in test mode

📚 Reference

  • Database Schema: sql/35_stripe_wrappers.sql
  • Webhook Handlers: sql/36_stripe_webhooks.sql
  • API Routes: api/src/stripe.ts
  • Webhook Handler: api/src/stripe-webhook.ts
  • Frontend Hooks: apps/main/src/hooks/stripe.ts
  • TypeScript Types: packages/shared/src/types/stripe.types.ts
  • Setup Guide: docs/STRIPE_SETUP.md
  • Stripe API Docs: https://stripe.com/docs/api
  • Supabase Wrappers: https://supabase.github.io/wrappers/stripe/

🎉 Benefits

  1. Direct Stripe Access - Query Stripe data without API calls
  2. Instant Status Checks - is_paying flag on user profile
  3. Automatic Sync - Webhooks keep data current
  4. Secure - RLS ensures data privacy
  5. Type-Safe - Full TypeScript support
  6. Scalable - Handles thousands of subscriptions
  7. Historical Data - Keep subscription history

🎯 Single "Standard" Plan Architecture

This implementation is optimized for a single subscription tier:

Plan Structure:

  • Free - Default tier, is_paying = false
  • Standard - Paid tier, is_paying = true, subscription_tier = 'standard'

Why This Works:

  • Simple pricing model
  • Easy to check: just user.is_paying
  • Future-proof: can add more tiers later by updating the tier logic
  • Webhook automatically handles upgrades/downgrades

API Endpoints Specific to Standard:

  • GET /api/v1/stripe/prices - Returns only "Standard" plan prices
  • Creates customers with metadata linking to user_id
  • All subscriptions automatically set tier to 'standard'

Implementation Status: Complete (Database, API, Frontend, Types)
Next Step: Configure Stripe and test webhook integration