Add docs for stripe

This commit is contained in:
Arthur Belleville 2025-11-02 08:56:31 +01:00
parent 40ce04ecc0
commit f6acbef13d
No known key found for this signature in database
4 changed files with 1152 additions and 0 deletions

View file

@ -0,0 +1,349 @@
# Stripe Integration Implementation Summary
## Overview
Complete Stripe integration for Xtablo with a single "Standard" subscription plan.
Uses custom Node.js API (Hono) with Supabase database for data storage.
## ✅ What Has Been Implemented
### 1. Database Schema (`sql/35_stripe_wrappers.sql`)
#### Foreign Tables (Direct Stripe API Access)
- `stripe.customers` - Query Stripe customers
- `stripe.subscriptions` - Query Stripe subscriptions
- `stripe.products` - Query Stripe products
- `stripe.prices` - Query Stripe prices
#### Local 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
- `POST /api/v1/stripe/create-checkout-session` - Start subscription checkout
- `POST /api/v1/stripe/create-portal-session` - Open customer portal
- `GET /api/v1/stripe/subscription` - Get user's subscription
- `GET /api/v1/stripe/is-paying` - Check payment status
- `GET /api/v1/stripe/prices` - Get Standard plan prices
- `POST /api/v1/stripe/cancel-subscription` - Cancel at period end
- `POST /api/v1/stripe/reactivate-subscription` - Reactivate subscription
**Features:**
- ✅ Automatic customer creation
- ✅ Secure webhook signature verification
- ✅ Complete subscription lifecycle management
- ✅ Customer portal access
- ✅ Subscription cancellation/reactivation
### 4. Frontend Hooks (`apps/main/src/hooks/stripe.ts`)
**Available Hooks:**
- `useSubscription()` - Get subscription details
- `useIsPayingUser()` - Check if user is paying
- `useStripePrices()` - Get available prices
- `useCreateCheckoutSession()` - Start checkout
- `useCreatePortalSession()` - Open portal
- `useCancelSubscription()` - Cancel subscription
- `useReactivateSubscription()` - Reactivate subscription
### 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 Function (Most Accurate)
```typescript
const { data: isPaying } = useQuery({
queryKey: ['is_paying', userId],
queryFn: async () => {
const { data } = await supabase.rpc('is_paying_user', {
user_uuid: userId
});
return data;
}
});
```
### Method 3: Direct 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
1. **User Clicks "Subscribe to Standard"**
→ Frontend calls `/api/v1/stripe/create-checkout-session`
2. **API Creates Checkout**
→ Creates/retrieves Stripe customer
→ Creates checkout session
→ Returns checkout URL
3. **User Completes Payment**
→ Stripe processes payment
→ Fires `customer.subscription.created` webhook
4. **Webhook Received**
→ API endpoint `/api/v1/stripe/webhook` receives event
→ Verifies signature
→ Calls `handle_stripe_subscription_upsert()`
5. **Database Updated**
→ Inserts into `stripe_subscriptions`
→ Trigger updates `profiles.is_paying = true` and `subscription_tier = 'standard'`
6. **Frontend Updates**
→ User's profile automatically shows paying status
`user.is_paying = true`
`user.subscription_tier = 'standard'`
## 🚀 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 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
7. **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

View file

@ -0,0 +1,190 @@
# Stripe Integration - Quick Reference
## 🚀 Quick Start (For Your Node.js API)
### 1. Install Dependencies
```bash
cd api && npm install stripe @stripe/stripe-js
cd apps/main && npm install @stripe/stripe-js
```
### 2. Environment Variables
**API (`.env`):**
```env
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
SUPABASE_SERVICE_ROLE_KEY=your_service_key
FRONTEND_URL=http://localhost:5173
```
**Frontend (`apps/main/.env`):**
```env
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
```
### 3. Stripe Dashboard Setup
1. Create product named **"Standard"**
2. Add pricing (save price IDs)
3. Add webhook: `https://your-api.com/api/v1/stripe/webhook`
4. Copy webhook secret
### 4. Run Migrations
```sql
-- Execute in Supabase SQL Editor
\i sql/35_stripe_wrappers.sql
\i sql/36_stripe_webhooks.sql
```
## 📋 API Endpoints
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/stripe/webhook` | ❌ | Stripe webhook (signature verified) |
| POST | `/api/v1/stripe/create-checkout-session` | ✅ | Start subscription flow |
| POST | `/api/v1/stripe/create-portal-session` | ✅ | Open customer portal |
| GET | `/api/v1/stripe/subscription` | ✅ | Get subscription details |
| GET | `/api/v1/stripe/is-paying` | ✅ | Check if user is paying |
| GET | `/api/v1/stripe/prices` | ❌ | Get Standard plan prices |
| POST | `/api/v1/stripe/cancel-subscription` | ✅ | Cancel subscription |
| POST | `/api/v1/stripe/reactivate-subscription` | ✅ | Reactivate subscription |
## 🎣 Frontend Hooks
```typescript
import {
useSubscription, // Get full subscription details
useIsPayingUser, // Boolean: is user paying?
useStripePrices, // Get available prices
useCreateCheckoutSession, // Create checkout & redirect
useCreatePortalSession, // Open customer portal
useCancelSubscription, // Cancel at period end
useReactivateSubscription // Undo cancellation
} from '@/hooks/stripe';
```
## 💡 Common Use Cases
### Check if User is Paying
```typescript
const user = useUser();
if (user.is_paying) {
// Show premium feature
}
```
### Subscribe to Standard Plan
```typescript
const { mutate: checkout } = useCreateCheckoutSession();
<button onClick={() => checkout({ priceId: 'price_xxxxx' })}>
Subscribe to Standard
</button>
```
### Manage Subscription
```typescript
const { mutate: openPortal } = useCreatePortalSession();
<button onClick={() => openPortal()}>
Manage Subscription
</button>
```
### Show Subscription Status
```typescript
const { data: subscription } = useSubscription();
{subscription?.status === 'active' && (
<div>
Active until {subscription.current_period_end}
{subscription.cancel_at_period_end && (
<span>Will cancel at period end</span>
)}
</div>
)}
```
## 🔍 Database Queries
### Check Subscription in SQL
```sql
-- Is user paying?
SELECT is_paying_user('user-uuid-here');
-- Get subscription details
SELECT * FROM get_user_subscription_status('user-uuid-here');
-- Get all active subscriptions
SELECT * FROM active_subscriptions;
-- Query Stripe directly
SELECT * FROM stripe.subscriptions WHERE customer = 'cus_xxxxx';
```
## 🎨 Profile Fields
After subscription sync, profiles have:
```typescript
{
is_paying: boolean, // true if active/trialing subscription
subscription_tier: string, // 'free' | 'standard'
}
```
## 🧪 Test Cards
| Card | Result |
|------|--------|
| `4242 4242 4242 4242` | ✅ Success |
| `4000 0000 0000 0002` | ❌ Declined |
| `4000 0000 0000 9995` | ❌ Insufficient funds |
## 🔔 Webhook Events Handled
- ✅ `customer.created`
- ✅ `customer.updated`
- ✅ `customer.deleted`
- ✅ `customer.subscription.created`
- ✅ `customer.subscription.updated`
- ✅ `customer.subscription.deleted`
- ✅ `product.created` / `updated` / `deleted`
- ✅ `price.created` / `updated` / `deleted`
## 🎯 Standard Plan Details
**Single Tier Model:**
- **Free**: Default (no subscription)
- **Standard**: Paid subscription
**Subscription automatically:**
- Sets `is_paying = true`
- Sets `subscription_tier = 'standard'`
- Updates on webhook events
- Reverts to free on cancellation
## 🔐 Security
- ✅ Webhook signature verification
- ✅ Row Level Security on all tables
- ✅ Users can only see their own subscriptions
- ✅ Service role for webhook functions
- ✅ Stripe keys in environment (never in code)
## 📞 Support
For issues, check:
1. API logs for webhook errors
2. Stripe Dashboard → Webhooks → Event logs
3. Supabase logs for database errors
4. Browser console for frontend errors
---
**Files:**
- Database: `sql/35_stripe_wrappers.sql` + `sql/36_stripe_webhooks.sql`
- Backend: `api/src/stripe.ts` + `api/src/stripe-webhook.ts`
- Frontend: `apps/main/src/hooks/stripe.ts`
- Types: `packages/shared/src/types/stripe.types.ts`

254
docs/STRIPE_README.md Normal file
View file

@ -0,0 +1,254 @@
# 💳 Stripe Integration for Xtablo
Complete Stripe subscription integration with a single "Standard" plan using your Node.js API.
## 📁 Files Created
### Database (SQL)
- `sql/35_stripe_wrappers.sql` - Database schema, tables, functions, triggers
- `sql/36_stripe_webhooks.sql` - Webhook handler functions
### Backend (Node.js API)
- `api/src/stripe.ts` - API routes for Stripe operations
- `api/src/stripe-webhook.ts` - Webhook event processor
- `api/src/routers.ts` - ✅ Updated with Stripe routes
### Frontend (React)
- `apps/main/src/hooks/stripe.ts` - React hooks for Stripe
- `apps/main/src/components/SubscriptionCard.tsx` - Ready-to-use subscription UI
- `packages/shared/src/types/stripe.types.ts` - TypeScript types
### Documentation
- `docs/STRIPE_SETUP.md` - Complete setup guide
- `docs/STRIPE_IMPLEMENTATION_SUMMARY.md` - Technical overview
- `docs/STRIPE_QUICK_REFERENCE.md` - Quick reference guide
- `docs/STRIPE_README.md` - This file
## ⚡ Quick Setup
### 1. Install Dependencies
```bash
cd api && npm install stripe @stripe/stripe-js
cd ../apps/main && npm install @stripe/stripe-js
```
### 2. Run Database Migrations
Execute in Supabase SQL Editor:
```sql
\i sql/35_stripe_wrappers.sql
\i sql/36_stripe_webhooks.sql
```
### 3. Configure Stripe Dashboard
1. Create product named **"Standard"**
2. Add monthly/yearly pricing
3. Save the price IDs
4. Add webhook endpoint: `https://your-api.com/api/v1/stripe/webhook`
5. Copy webhook signing secret
### 4. Set Environment Variables
**API (`.env`):**
```env
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
SUPABASE_SERVICE_ROLE_KEY=your_key
FRONTEND_URL=http://localhost:5173
```
**Frontend (`apps/main/.env`):**
```env
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
VITE_STRIPE_STANDARD_MONTHLY_PRICE_ID=price_xxxxx
```
### 5. Add Subscription Card to Settings
In `apps/main/src/pages/settings.tsx`:
```typescript
import { SubscriptionCard } from "../components/SubscriptionCard";
// Inside your settings page JSX, add:
<SubscriptionCard />
```
## 🎯 How It Works
### User Journey
1. **Free User** sees upgrade prompt in SubscriptionCard
2. Clicks "Passer à Standard"
3. Redirected to Stripe Checkout
4. Completes payment
5. Redirected back to settings with `?success=true`
6. Webhook updates database
7. `user.is_paying` becomes `true`
8. `user.subscription_tier` becomes `'standard'`
9. UI automatically updates
### Data Sync
```
Stripe → Webhook → API → Database → Frontend
profiles.is_paying = true
```
## 🎨 Using Subscription Card
The `SubscriptionCard` component shows:
**For Free Users:**
- "Plan Gratuit" badge
- "Passer à Standard" button
- Initiates Stripe Checkout
**For Paying Users:**
- "Actif" badge
- Current subscription details
- Renewal date
- "Gérer l'abonnement" button (opens Stripe portal)
- "Annuler" button
**For Canceling Users:**
- "Annulation en cours" warning
- Access until period end
- "Réactiver l'abonnement" button
## 🔍 Check Payment Status Anywhere
```typescript
import { useUser } from '../providers/UserStoreProvider';
function PremiumFeature() {
const user = useUser();
if (!user.is_paying) {
return <UpgradePrompt />;
}
return <PremiumContent />;
}
```
## 🎣 Available Hooks
```typescript
// Get subscription details
const { data: subscription } = useSubscription();
// Check if paying (boolean)
const { data: isPaying } = useIsPayingUser();
// Get available prices
const { data: prices } = useStripePrices();
// Create checkout session
const { mutate: checkout } = useCreateCheckoutSession();
checkout({ priceId: 'price_xxxxx' });
// Open customer portal
const { mutate: portal } = useCreatePortalSession();
portal();
// Cancel subscription
const { mutate: cancel } = useCancelSubscription();
cancel();
// Reactivate subscription
const { mutate: reactivate } = useReactivateSubscription();
reactivate();
```
## 🧪 Testing
### Test Cards
```
Success: 4242 4242 4242 4242
Decline: 4000 0000 0000 0002
```
### Test Webhooks Locally
```bash
stripe listen --forward-to http://localhost:3000/api/v1/stripe/webhook
```
### Trigger Events
```bash
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deleted
```
## 🔐 Security Features
✅ Webhook signature verification
✅ Row Level Security on all tables
✅ Users can only see their own subscriptions
✅ API keys never exposed to frontend
✅ Service role for database operations
## 📊 Database Schema
### Tables
- `stripe_customers` - Links Stripe customers to users
- `stripe_subscriptions` - Subscription records
- `stripe_products` - Product catalog
- `stripe_prices` - Pricing information
### Profile Fields
- `is_paying: boolean` - Quick check for payment status
- `subscription_tier: 'free' | 'standard'` - Current tier
### Functions
- `is_paying_user(uuid)` - Returns boolean
- `get_user_subscription_status(uuid)` - Returns subscription details
- `get_user_stripe_customer_id(uuid)` - Returns Stripe customer ID
## 🐛 Troubleshooting
**Webhooks not working?**
1. Check API logs
2. Verify webhook secret in `.env`
3. Test with Stripe CLI
4. Check Stripe Dashboard → Webhooks for delivery status
**Subscription not showing?**
1. Check `stripe_subscriptions` table in Supabase
2. Verify webhook was received
3. Check `profiles.is_paying` field
4. Ensure customer has `user_id` in metadata
**Can't create checkout?**
1. Verify `STRIPE_SECRET_KEY` is set
2. Check `VITE_STRIPE_PUBLISHABLE_KEY` in frontend
3. Ensure price ID is correct
4. Check browser console for errors
## 📞 Support
See detailed docs:
- **Setup Guide**: `docs/STRIPE_SETUP.md`
- **Technical Details**: `docs/STRIPE_IMPLEMENTATION_SUMMARY.md`
- **Quick Reference**: `docs/STRIPE_QUICK_REFERENCE.md`
## 🎉 Next Steps
1. ✅ Database schema created
2. ✅ API endpoints implemented
3. ✅ Frontend hooks ready
4. ✅ UI component created
5. ⏳ Run migrations
6. ⏳ Configure Stripe
7. ⏳ Set environment variables
8. ⏳ Test with test cards
9. ⏳ Add SubscriptionCard to settings page
---
**Status**: ✅ Implementation Complete
**Architecture**: Node.js API + Supabase Database
**Plan**: Single "Standard" tier
**Ready to Deploy**: Yes, follow setup guide

359
docs/STRIPE_SETUP.md Normal file
View file

@ -0,0 +1,359 @@
# Stripe Integration Setup Guide
This guide walks you through setting up Stripe payments integration for Xtablo with a single "Standard" plan.
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Database Setup](#database-setup)
3. [Stripe Configuration](#stripe-configuration)
4. [Backend API Setup](#backend-api-setup)
5. [Frontend Integration](#frontend-integration)
6. [Testing](#testing)
7. [Usage Examples](#usage-examples)
## Prerequisites
- Supabase project with database access
- Stripe account (test mode for development)
- Supabase Wrappers extension enabled
- Node.js API already running
## Database Setup
### 1. Enable Wrappers Extension
In your Supabase SQL Editor, enable the Wrappers extension:
```sql
create extension if not exists wrappers with schema extensions;
```
### 2. Store Stripe API Key in Vault
```sql
select vault.create_secret(
'sk_test_xxxxx', -- Your Stripe secret key (test or live)
'stripe',
'Stripe API key for Wrappers'
);
```
**Note the `key_id` returned** - you'll need this for the next step.
### 3. Run Migration Scripts
Execute the SQL migration files in order:
```bash
# From your Supabase SQL Editor
1. Run: sql/35_stripe_wrappers.sql
2. Run: sql/36_stripe_webhooks.sql
```
### 4. Update API Key ID
In `35_stripe_wrappers.sql`, update line 27 with your actual `key_id` from step 2:
```sql
create server stripe_server
foreign data wrapper stripe_wrapper
options (
api_key_id 'YOUR_KEY_ID_HERE', -- Update this!
api_url 'https://api.stripe.com/v1/',
api_version '2024-06-20'
);
```
## Stripe Configuration
### 1. Create Products and Prices
In your Stripe Dashboard:
1. Go to **Products**
2. Click **Add product**
3. Create your subscription tiers (e.g., "Premium", "Pro")
4. Add pricing (monthly, yearly, etc.)
5. Note the `price_id` (starts with `price_xxx`)
### 2. Create the "Standard" Plan
In your Stripe Dashboard:
1. Go to **Products**
2. Click **Add product**
3. Name: **"Standard"** (important: must match exactly)
4. Add your pricing:
- Monthly: e.g., €9.99/month
- Yearly: e.g., €99/year (optional)
5. **Save the price IDs** (e.g., `price_xxxxx`)
### 3. Set up Webhooks
1. Go to **Developers → Webhooks** in Stripe Dashboard
2. Click **Add endpoint**
3. URL: `https://YOUR_API_URL/api/v1/stripe/webhook`
4. Select events to listen to:
- `customer.created`
- `customer.updated`
- `customer.deleted`
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `product.created`
- `product.updated`
- `product.deleted`
- `price.created`
- `price.updated`
- `price.deleted`
5. **Copy the webhook signing secret** (starts with `whsec_`)
## Backend API Setup
The backend API handlers are already implemented in:
- `api/src/stripe.ts` - Main Stripe routes
- `api/src/stripe-webhook.ts` - Webhook handler
- `api/src/routers.ts` - Router configuration
### 1. Install Stripe SDK
```bash
cd api
npm install stripe @stripe/stripe-js
```
### 2. Configure Environment Variables
Add to your `.env` file:
```env
# Stripe
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
# For webhooks to work
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
```
### 3. Restart API Server
The Stripe routes are automatically registered at:
- `POST /api/v1/stripe/webhook` - Webhook endpoint
- `POST /api/v1/stripe/create-checkout-session` - Create checkout
- `POST /api/v1/stripe/create-portal-session` - Customer portal
- `GET /api/v1/stripe/subscription` - Get subscription status
- `GET /api/v1/stripe/is-paying` - Check if paying
- `GET /api/v1/stripe/prices` - Get available prices
- `POST /api/v1/stripe/cancel-subscription` - Cancel subscription
- `POST /api/v1/stripe/reactivate-subscription` - Reactivate subscription
## Frontend Integration
The frontend hooks are already implemented in `apps/main/src/hooks/stripe.ts`.
### 1. Install Stripe SDK
```bash
cd apps/main
npm install @stripe/stripe-js
```
### 2. Configure Environment Variable
Add to `apps/main/.env`:
```env
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
```
### 3. Available Hooks
All hooks are ready to use:
```typescript
import {
useSubscription, // Get subscription details
useIsPayingUser, // Check if user is paying
useStripePrices, // Get available prices
useCreateCheckoutSession, // Start checkout flow
useCreatePortalSession, // Open customer portal
useCancelSubscription, // Cancel subscription
useReactivateSubscription, // Reactivate canceled subscription
} from "../hooks/stripe";
```
## Usage Examples
### Check if User is Paying
```typescript
// In any component
const { data: isPaying } = useIsPayingUser();
if (!isPaying) {
return <UpgradePrompt />;
}
```
### Get Subscription Details
```typescript
const { data: subscription } = useSubscription();
if (subscription) {
console.log("Status:", subscription.status);
console.log("Plan:", subscription.product_name);
console.log("Renews:", subscription.current_period_end);
}
```
### Access Subscription Status from Profile
```typescript
// The user profile already has is_paying and subscription_tier fields
const user = useUser();
if (user.is_paying) {
console.log("User is on:", user.subscription_tier);
}
```
### Create Checkout Session
```typescript
import { useCreateCheckoutSession } from "../hooks/stripe";
function UpgradeButton() {
const { mutate: createCheckout, isPending } = useCreateCheckoutSession();
return (
<button
onClick={() =>
createCheckout({
priceId: "price_xxxxx", // Your Standard plan price ID
})
}
disabled={isPending}
>
{isPending ? "Loading..." : "Subscribe to Standard"}
</button>
);
}
```
### Manage Subscription (Customer Portal)
```typescript
import { useCreatePortalSession } from "../hooks/stripe";
function ManageSubscriptionButton() {
const { mutate: openPortal, isPending } = useCreatePortalSession();
return (
<button
onClick={() => openPortal(window.location.href)}
disabled={isPending}
>
Manage Subscription
</button>
);
}
```
### Cancel Subscription
```typescript
import { useCancelSubscription } from "../hooks/stripe";
function CancelButton() {
const { mutate: cancel, isPending } = useCancelSubscription();
return (
<button onClick={() => cancel()} disabled={isPending}>
Cancel Subscription
</button>
);
}
```
## Testing
### 1. Use Stripe Test Mode
Use test cards from [Stripe Testing](https://stripe.com/docs/testing):
- Success: `4242 4242 4242 4242`
- Decline: `4000 0000 0000 0002`
### 2. Test Webhooks Locally
```bash
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to local API
stripe listen --forward-to http://localhost:3000/api/v1/stripe/webhook
```
### 3. Trigger Test Events
```bash
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deleted
```
## Security Checklist
- [ ] Stripe API keys stored in Supabase Vault
- [ ] Webhook signing secret configured
- [ ] Row Level Security (RLS) enabled on all tables
- [ ] Edge Function uses service role key
- [ ] Customer metadata includes user_id for mapping
- [ ] Test mode used for development
## Troubleshooting
### Webhooks Not Working
1. Check webhook endpoint URL is correct (`https://your-api/api/v1/stripe/webhook`)
2. Verify signing secret matches in `.env`
3. Check API logs for webhook errors
4. Ensure `SUPABASE_SERVICE_ROLE_KEY` is set in API environment
5. Test with Stripe CLI: `stripe trigger customer.subscription.created`
### Subscription Not Showing
1. Verify customer has `user_id` in metadata
2. Check webhook was received (Stripe Dashboard → Webhooks)
3. Query `stripe_subscriptions` table directly
4. Check `profiles.is_paying` was updated by trigger
### Foreign Tables Empty
1. Verify Stripe API key is correct
2. Check key_id in server configuration
3. Test with: `SELECT * FROM stripe.customers LIMIT 5;`
4. Ensure Wrappers extension is enabled
## Next Steps
1. Create pricing page component
2. Build subscription management UI
3. Implement usage-based billing (if needed)
4. Add customer portal for self-service
5. Set up email notifications for billing events
---
For more information, see:
- [Supabase Stripe Integration](https://supabase.com/docs/guides/integrations/stripe)
- [Stripe Documentation](https://stripe.com/docs)
- [Supabase Wrappers](https://supabase.github.io/wrappers/)