Stripe Payments
Implementing secure payments and paywalls with Stripe
Stripe powers our payment system, offering robust processing, subscription management, and global support.
Stripe Setup
- Create a Stripe account or sign in.
- Configure your account and add products or subscription plans.
Product and Payment Link Creation
- Navigate to
Product Catalogue
in the Stripe Dashboard. - Click “Create product” and input details. For our credit system, we’ll use one-off payments.
- On the product page, create price variations (e.g., credits-small for 50 credits, credits-large for 100 credits).
- Select “Create payment link” and configure settings.
- Add custom metadata in the payment link settings:
- Click “Add metadata”
- Set a key (e.g., “type”) and value (e.g., “credits-large”)
This metadata is crucial for determining the purchase type in your webhook handler.
Webhook Configuration
Stripe Webhook Secret
- Go to
Developers -> Webhooks
in the Stripe Dashboard. - Click “Add endpoint”.
- Set the Endpoint URL to
https://yourwebsite.com/api/payments/stripe
. - Select events to listen for (at minimum, choose “checkout.session.completed”).
- Copy the Signing secret and add it to your
.env
file asSTRIPE_WEBHOOK_SECRET
.
Also add your Stripe secret key to .env
as STRIPE_SECRET_KEY
. Find this
under Developers -> API Keys.
Webhook Handling
The API route at app/api/payments/stripe/route.ts
manages Stripe payments and subscriptions:
- Verifies request legitimacy using
STRIPE_WEBHOOK_SECRET
. - Processes the “checkout.session.completed” event.
- Retrieves product information and determines purchase type from metadata.
- Matches user email with the
profiles
table:- For matching users, updates the
purchase
column inprofiles
. - For non-matching users, records the purchase in
purchases
. - Performs specific actions based on metadata (e.g., adding user credits).
- For matching users, updates the
Database Setup
The purchases
table should already exist if you followed the Quick Setup guide and ran the Supabase migrations using the CLI:
If you haven’t done this yet, please go back to the Quick Setup guide first. Alternatively, you can manually create the table by running the SQL from this file in your Supabase SQL editor:
supabase/migrations/20240000000007_purchases.sql
Make sure RLS is turned on. We don’t need to add any rules as we’ll be using the service key for to interact with this table.
Purchase Type Determination
The webhook handler identifies the purchase type in this order:
- Checks
session.metadata.type
(set in payment link). - If not found, checks
product.metadata.type
. - If still not found, infers from the product name (premium, core, or enterprise).
This prioritization allows precise control over purchase types via payment link metadata.
User Profile Updates
After a successful purchase, the webhook handler:
- Records the purchase in the
purchases
table. - Updates the user’s profile in the
profiles
table with the new purchase type.
Customize the updateUserProfile
function in @/lib/hooks/userData
to handle
various purchase or subscription types based on metadata.
Paywall Implementation
Demo apps include a credit-based paywall system that uses cached queries for better performance. Here’s how it works:
The paywall logic:
- Check if the demo app is paywalled (defined in
toolConfig.ts
). - Uses cached queries to efficiently verify user credit balance:
- If sufficient, grant access.
- If insufficient, display the
PaymentModal
.
The cached queries system helps reduce database load and improve performance compared to direct Supabase queries.
Payment Flow Implementation
- Create a UI element linking to your Stripe payment URL.
- Stripe sends a webhook to your endpoint upon purchase completion.
- Your webhook handler processes the payment, using metadata to determine the purchase type.
- Update the user’s profile in your database with purchase information.
- Grant access to premium features based on the updated profile.
This flow, combined with payment link metadata, enables easy management of product tiers and database updates for purchases.