From f6acbef13d570aae5a851774934abc6ebac03c1d Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sun, 2 Nov 2025 08:56:31 +0100 Subject: [PATCH] Add docs for stripe --- docs/STRIPE_IMPLEMENTATION_SUMMARY.md | 349 +++++++++++++++++++++++++ docs/STRIPE_QUICK_REFERENCE.md | 190 ++++++++++++++ docs/STRIPE_README.md | 254 ++++++++++++++++++ docs/STRIPE_SETUP.md | 359 ++++++++++++++++++++++++++ 4 files changed, 1152 insertions(+) create mode 100644 docs/STRIPE_IMPLEMENTATION_SUMMARY.md create mode 100644 docs/STRIPE_QUICK_REFERENCE.md create mode 100644 docs/STRIPE_README.md create mode 100644 docs/STRIPE_SETUP.md diff --git a/docs/STRIPE_IMPLEMENTATION_SUMMARY.md b/docs/STRIPE_IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..c0912fd --- /dev/null +++ b/docs/STRIPE_IMPLEMENTATION_SUMMARY.md @@ -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 ; + } + ``` + +## 📝 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 + diff --git a/docs/STRIPE_QUICK_REFERENCE.md b/docs/STRIPE_QUICK_REFERENCE.md new file mode 100644 index 0000000..41b26d0 --- /dev/null +++ b/docs/STRIPE_QUICK_REFERENCE.md @@ -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(); + + +``` + +### Manage Subscription +```typescript +const { mutate: openPortal } = useCreatePortalSession(); + + +``` + +### Show Subscription Status +```typescript +const { data: subscription } = useSubscription(); + +{subscription?.status === 'active' && ( +
+ Active until {subscription.current_period_end} + {subscription.cancel_at_period_end && ( + Will cancel at period end + )} +
+)} +``` + +## 🔍 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` + diff --git a/docs/STRIPE_README.md b/docs/STRIPE_README.md new file mode 100644 index 0000000..a102c79 --- /dev/null +++ b/docs/STRIPE_README.md @@ -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: + +``` + +## 🎯 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 ; + } + + return ; +} +``` + +## 🎣 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 + diff --git a/docs/STRIPE_SETUP.md b/docs/STRIPE_SETUP.md new file mode 100644 index 0000000..e27ff16 --- /dev/null +++ b/docs/STRIPE_SETUP.md @@ -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 ; +} +``` + +### 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 ( + + ); +} +``` + +### Manage Subscription (Customer Portal) + +```typescript +import { useCreatePortalSession } from "../hooks/stripe"; + +function ManageSubscriptionButton() { + const { mutate: openPortal, isPending } = useCreatePortalSession(); + + return ( + + ); +} +``` + +### Cancel Subscription + +```typescript +import { useCancelSubscription } from "../hooks/stripe"; + +function CancelButton() { + const { mutate: cancel, isPending } = useCancelSubscription(); + + return ( + + ); +} +``` + +## 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/)