394 lines
11 KiB
Markdown
394 lines
11 KiB
Markdown
# 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
|
|
|
|
```sql
|
|
-- 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)
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
const { data: isPaying } = useIsPayingUser();
|
|
// Returns user.is_paying directly
|
|
```
|
|
|
|
### Method 3: Direct Supabase Query
|
|
|
|
```typescript
|
|
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 Supabase**
|
|
→ `useSubscription()` 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**
|
|
|
|
```bash
|
|
cd apps/api
|
|
npm install stripe @stripe/stripe-js
|
|
```
|
|
|
|
3. **Configure API Environment Variables**
|
|
Add to `api/.env`:
|
|
|
|
```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`:
|
|
|
|
```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:
|
|
|
|
7. **Hooks Already Created** ✅
|
|
|
|
- `useSubscription()` - Get subscription details
|
|
- `useIsPayingUser()` - Check payment status
|
|
- `useCreateCheckoutSession()` - Initiate checkout
|
|
- `useCreatePortalSession()` - Customer portal
|
|
- `useCancelSubscription()` - Cancel subscription
|
|
- `useReactivateSubscription()` - Reactivate
|
|
|
|
8. **Build UI Components**
|
|
|
|
- Pricing page
|
|
- Subscription management page
|
|
- Payment status badges
|
|
- Upgrade prompts
|
|
|
|
9. **Add Feature Gates**
|
|
```typescript
|
|
if (!user.is_paying) {
|
|
return <PremiumFeatureLockedPrompt />;
|
|
}
|
|
```
|
|
|
|
## 📝 Environment Variables Needed
|
|
|
|
### API (`api/.env`)
|
|
|
|
```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`)
|
|
|
|
```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
|