xtablo-source/docs/STRIPE_WITH_SYNC_ENGINE.md
Arthur Belleville 2e16353f5e
etape color
2025-11-19 22:24:23 +01:00

6.9 KiB

Stripe Integration Using @supabase/stripe-sync-engine

📦 Overview

We're using the official @supabase/stripe-sync-engine library to handle all Stripe webhook processing and data syncing.

Benefits:

  • Battle-tested by Supabase
  • Handles all Stripe webhook events automatically
  • Automatic schema management
  • Real-time sync of Stripe data
  • No custom webhook handlers needed

🚀 Quick Setup

1. Install Package

cd apps/api
npm install @supabase/stripe-sync-engine

2. Configure Environment

Add to api/.env:

# Stripe
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx

# Database (direct Postgres connection)
DATABASE_URL=postgresql://postgres:[password]@db.[project].supabase.co:5432/postgres

# Or use Supabase connection string
SUPABASE_URL=https://[project].supabase.co
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

3. Run Library Migrations

The stripe-sync-engine library comes with its own migrations. Run them:

// In a one-time setup script or manually
import { runMigrations } from '@supabase/stripe-sync-engine';

await runMigrations({ 
  databaseUrl: process.env.DATABASE_URL 
});

Or manually execute the migrations from: node_modules/@supabase/stripe-sync-engine/dist/migrations/*.sql

4. Integration is Already Done!

The webhook handler in api/src/stripe.ts is already configured:

import { StripeSync } from "@supabase/stripe-sync-engine";

const stripeSync = new StripeSync({
  stripeSecretKey: process.env.STRIPE_SECRET_KEY,
  stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
  schema: "public",
  poolConfig: {
    connectionString: process.env.DATABASE_URL,
    max: 10,
  },
  revalidateObjectsViaStripeApi: ['subscription', 'customer'],
});

// Webhook endpoint
stripeRouter.post("/webhook", async (c) => {
  const signature = c.req.header("stripe-signature");
  const rawBody = await c.req.text();
  
  await stripeSync.processWebhook(rawBody, signature);
  
  return c.json({ received: true });
});

📊 Database Schema

The library creates these tables in the public schema (with stripe_ prefix):

Core Tables (Auto-created)

  • stripe_customers
  • stripe_subscriptions
  • stripe_subscription_items
  • stripe_products
  • stripe_prices
  • stripe_invoices
  • stripe_charges
  • stripe_payment_intents
  • stripe_payment_methods
  • And many more...

Custom Additions (From our SQL)

We still need our custom sql/35_stripe_wrappers.sql for:

  • profiles.is_paying column
  • profiles.subscription_tier column
  • Triggers to auto-update profile when subscription changes
  • RLS policies
  • Helper functions (is_paying_user(), etc.)

Note: Delete sql/36_stripe_webhooks.sql - not needed, library handles webhooks!

🔄 How It Works

1. Webhook Received

Stripe → POST /api/v1/stripe/webhook → StripeSync.processWebhook()

2. Sync Engine Processes

1. Verifies signature
2. Determines event type
3. Syncs to appropriate table (stripe_subscriptions, stripe_customers, etc.)
4. Handles all edge cases automatically

3. Our Custom Trigger Fires

stripe_subscriptions updated → Trigger → profiles.is_paying updated

4. Frontend Queries

Frontend → Supabase Client → stripe_subscriptions (RLS) → User's data

What the Library Handles

The stripe-sync-engine automatically handles:

  • All webhook event types (100+ events)
  • Signature verification
  • Idempotency (duplicate events)
  • Foreign key relationships
  • Deleted entities
  • List expansion (fetching all items)
  • Backfilling historical data
  • Database schema migrations
  • Connection pooling
  • Error handling

🎯 What We Still Handle

We only need to maintain:

  1. Custom Profile Fields

    • profiles.is_paying
    • profiles.subscription_tier
  2. Automatic Update Trigger

    CREATE TRIGGER update_profile_on_subscription_change
      AFTER INSERT OR UPDATE ON stripe_subscriptions
       Updates profile fields
    
  3. Action Endpoints (in api/src/stripe.ts)

    • Create checkout session
    • Create portal session
    • Cancel subscription
    • Reactivate subscription
  4. RLS Policies

    • Ensure users only see their own data

🧪 Testing

Test Webhook Processing

# Terminal 1: Start your API
cd apps/api && npm run dev

# Terminal 2: Forward Stripe webhooks
stripe listen --forward-to http://localhost:3000/api/v1/stripe/webhook

# Terminal 3: Trigger test event
stripe trigger customer.subscription.created

Watch the magic:

  1. Webhook arrives
  2. stripe-sync-engine processes it
  3. Data appears in stripe_subscriptions table
  4. Trigger updates profiles.is_paying
  5. Frontend sees update immediately

Verify Sync

-- Check synced subscription
SELECT * FROM stripe_subscriptions;

-- Check synced customer
SELECT * FROM stripe_customers;

-- Check profile updated
SELECT email, is_paying, subscription_tier FROM profiles;

📝 Environment Variables Needed

# Required
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
DATABASE_URL=postgresql://postgres:password@host:port/db

# Optional (if DATABASE_URL not set)
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJxxx...

🔧 Backfilling Historical Data

If you have existing Stripe data, backfill it:

// Run this once to sync all existing data
await stripeSync.syncBackfill({
  object: 'all', // or specific: 'customer', 'subscription', 'product', etc.
  created: {
    gte: 0 // Sync everything from the beginning
  }
});

Or sync specific objects:

await stripeSync.syncProducts();
await stripeSync.syncPrices();
await stripeSync.syncCustomers();
await stripeSync.syncSubscriptions();

📚 Resources

Benefits Over Custom Implementation

Feature Custom stripe-sync-engine
Webhook handlers Manual Automatic
Schema management Manual Automatic
Edge cases You handle Handled
Updates You maintain Library updates
Testing Your responsibility Battle-tested
Backfilling Custom scripts Built-in

🎉 Summary

Before: 267 lines of custom webhook code + SQL handlers
After: ~20 lines using the library

What you maintain:

  • Profile integration (is_paying, subscription_tier)
  • Action endpoints (checkout, portal, cancel)
  • RLS policies
  • Frontend hooks

What the library handles:

  • Everything else! 🚀

Status: Integrated
Next: Configure Stripe, test webhooks, add to settings page