xtablo-source/docs/STRIPE_SETUP.md
2025-11-10 08:53:03 +01:00

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

  1. Prerequisites
  2. Database Setup
  3. Stripe Configuration
  4. Backend API Setup
  5. Frontend Integration
  6. Testing
  7. 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:

  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

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 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

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

  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: