347 lines
8.6 KiB
Markdown
347 lines
8.6 KiB
Markdown
# 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. Run Migration Scripts
|
|
|
|
Execute the SQL migration files in your Supabase SQL Editor:
|
|
|
|
```sql
|
|
-- Execute these in order
|
|
\i sql/35_stripe_wrappers.sql -- Creates tables, functions, RLS policies
|
|
\i sql/36_stripe_webhooks.sql -- Creates webhook handler functions
|
|
```
|
|
|
|
These migrations create:
|
|
|
|
- ✅ Subscription tracking tables (`stripe_customers`, `stripe_subscriptions`, `stripe_products`, `stripe_prices`)
|
|
- ✅ Helper functions (`is_paying_user()`, `get_user_subscription_status()`)
|
|
- ✅ Automatic triggers to update `profiles.is_paying`
|
|
- ✅ Row Level Security policies
|
|
- ✅ Webhook handler functions
|
|
|
|
**No Wrappers needed!** This is a pure webhook-based integration.
|
|
|
|
## 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 apps/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 {
|
|
// Direct Supabase queries (RLS-protected)
|
|
useSubscription, // Get subscription from Supabase
|
|
useIsPayingUser, // Check if paying (from user.is_paying)
|
|
useStripePrices, // Get prices from Supabase
|
|
|
|
// API calls (for actions)
|
|
useCreateCheckoutSession, // Start checkout
|
|
useCreatePortalSession, // Open customer portal
|
|
useCancelSubscription, // Cancel subscription
|
|
useReactivateSubscription, // Reactivate subscription
|
|
} from "../hooks/stripe";
|
|
```
|
|
|
|
**Architecture:**
|
|
|
|
- 📖 **Reads**: Direct Supabase queries (fast, RLS-protected)
|
|
- ✍️ **Writes**: API calls to Stripe → Webhooks update Supabase
|
|
|
|
## Usage Examples
|
|
|
|
### Check if User is Paying
|
|
|
|
```typescript
|
|
// In any component
|
|
const { data: isPaying } = useIsPayingUser();
|
|
|
|
if (!isPaying) {
|
|
return <UpgradePrompt />;
|
|
}
|
|
```
|
|
|
|
### Get Subscription Details (Queries Supabase Directly)
|
|
|
|
```typescript
|
|
const { data: subscription } = useSubscription();
|
|
|
|
if (subscription) {
|
|
console.log("Status:", subscription.status);
|
|
console.log("Renews:", subscription.current_period_end);
|
|
console.log("Will cancel?:", subscription.cancel_at_period_end);
|
|
|
|
// Access related product/price via join
|
|
console.log("Product:", subscription.price?.product?.name);
|
|
console.log("Amount:", subscription.price?.unit_amount);
|
|
}
|
|
```
|
|
|
|
### 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/)
|