8.6 KiB
8.6 KiB
Stripe Integration Setup Guide
This guide walks you through setting up Stripe payments integration for Xtablo with a single "Standard" plan.
Table of Contents
- Prerequisites
- Database Setup
- Stripe Configuration
- Backend API Setup
- Frontend Integration
- Testing
- 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:
-- 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:
- Go to Products
- Click Add product
- Create your subscription tiers (e.g., "Premium", "Pro")
- Add pricing (monthly, yearly, etc.)
- Note the
price_id(starts withprice_xxx)
2. Create the "Standard" Plan
In your Stripe Dashboard:
- Go to Products
- Click Add product
- Name: "Standard" (important: must match exactly)
- Add your pricing:
- Monthly: e.g., €9.99/month
- Yearly: e.g., €99/year (optional)
- Save the price IDs (e.g.,
price_xxxxx)
3. Set up Webhooks
- Go to Developers → Webhooks in Stripe Dashboard
- Click Add endpoint
- URL:
https://YOUR_API_URL/api/v1/stripe/webhook - Select events to listen to:
customer.createdcustomer.updatedcustomer.deletedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedproduct.createdproduct.updatedproduct.deletedprice.createdprice.updatedprice.deleted
- 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 routesapi/src/stripe-webhook.ts- Webhook handlerapi/src/routers.ts- Router configuration
1. Install Stripe SDK
cd apps/api
npm install stripe @stripe/stripe-js
2. Configure Environment Variables
Add to your .env file:
# 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 endpointPOST /api/v1/stripe/create-checkout-session- Create checkoutPOST /api/v1/stripe/create-portal-session- Customer portalGET /api/v1/stripe/subscription- Get subscription statusGET /api/v1/stripe/is-paying- Check if payingGET /api/v1/stripe/prices- Get available pricesPOST /api/v1/stripe/cancel-subscription- Cancel subscriptionPOST /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
cd apps/main
npm install @stripe/stripe-js
2. Configure Environment Variable
Add to apps/main/.env:
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
3. Available Hooks
All hooks are ready to use:
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
// In any component
const { data: isPaying } = useIsPayingUser();
if (!isPaying) {
return <UpgradePrompt />;
}
Get Subscription Details (Queries Supabase Directly)
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
// 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
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)
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
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:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002
2. Test Webhooks Locally
# 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
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
- Check webhook endpoint URL is correct (
https://your-api/api/v1/stripe/webhook) - Verify signing secret matches in
.env - Check API logs for webhook errors
- Ensure
SUPABASE_SERVICE_ROLE_KEYis set in API environment - Test with Stripe CLI:
stripe trigger customer.subscription.created
Subscription Not Showing
- Verify customer has
user_idin metadata - Check webhook was received (Stripe Dashboard → Webhooks)
- Query
stripe_subscriptionstable directly - Check
profiles.is_payingwas updated by trigger
Foreign Tables Empty
- Verify Stripe API key is correct
- Check key_id in server configuration
- Test with:
SELECT * FROM stripe.customers LIMIT 5; - Ensure Wrappers extension is enabled
Next Steps
- Create pricing page component
- Build subscription management UI
- Implement usage-based billing (if needed)
- Add customer portal for self-service
- Set up email notifications for billing events
For more information, see: