LovableStripeIntermediate18 min read

How to Add Stripe Payments to Your Lovable App

A detailed, step-by-step guide to adding Stripe payments to your Lovable app. Covers products, prices, Supabase tables, checkout sessions, webhooks, testing, and going live safely.

Before you start

  • A Lovable account with an existing app
  • A Stripe account in test mode
  • A Supabase project connected to your Lovable app
  • A product or plan you want to sell (one-time or subscription)

Step by step

1

Decide what Stripe flow you actually need

Before building anything, choose the payment model. Use one-time Checkout for templates, audits, and digital downloads. Use subscription Checkout for SaaS plans or memberships. The Stripe setup is similar, but your products, webhook events, and database fields differ.

# Use one-time payment if you sell:
# - templates
# - reports
# - single downloads
# - one-off services

# Use subscription billing if you sell:
# - SaaS access
# - memberships
# - recurring coaching or retainers
2

Create your Stripe product and copy the price ID

In Stripe Dashboard, create the exact product and price you will charge. For subscriptions, create a recurring monthly or annual price. For one-time payments, create a one-time price. Copy the resulting price ID because your Lovable app will use it directly.

# Stripe Dashboard → Products → Add product
# Example:
# Name: Pro Plan
# Pricing model: Recurring
# Price: $29/month
# Copy the generated price ID:
price_1234567890abcdef
3

Add the Stripe secrets to Supabase

Stripe secret keys and webhook secrets must live server-side. In Supabase, go to Edge Functions → Secrets and add them there. Keep using test keys until you finish the full payment flow.

STRIPE_SECRET_KEY=sk_test_your_secret_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
NEXT_PUBLIC_APP_URL=https://your-app-url.com
4

Create the database table that tracks payments or access

Your app needs somewhere to store what Stripe changed. Ask Lovable to create a payments or subscriptions table before wiring the webhook. This avoids the common problem where payments succeed in Stripe but your app has nowhere to save the result.

Paste this into Lovable:

Create a Supabase table for payment tracking.
If this is a subscription app, create a 'subscriptions' table with columns:
- id
- user_id
- stripe_customer_id
- stripe_subscription_id
- stripe_price_id
- status
- current_period_end
- created_at
- updated_at

If this is a one-time payment flow, create a 'payments' table with columns:
- id
- user_id
- stripe_checkout_session_id
- stripe_payment_intent_id
- stripe_price_id
- amount
- currency
- status
- created_at

Add RLS so users can only read their own rows.
5

Create the checkout Edge Function in Supabase

This is the server-side function that creates the Stripe Checkout session. It should accept the active user's email and the Stripe price ID, create or reuse a Stripe customer, then return the checkout URL.

Paste this into Lovable:

Create a Supabase Edge Function called 'create-checkout' that:
1. Accepts price_id and user_email in the request body
2. Creates or finds a Stripe customer by email
3. Creates a Stripe Checkout session
4. Uses mode 'payment' for one-time purchases or 'subscription' for recurring plans
5. Sets success_url to `${NEXT_PUBLIC_APP_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`
6. Sets cancel_url to `${NEXT_PUBLIC_APP_URL}/pricing`
7. Adds the user_id and price_id in Stripe metadata
8. Returns the checkout URL as JSON
9. Handles errors with clear messages
10. Adds CORS headers so the frontend can call it
6

Build the pricing or checkout UI in Lovable

Now create the page and button that calls your Edge Function. The user should click once, see a loading state, and then be redirected to Stripe Checkout. Do not expose secret keys here. Only call the backend function.

Paste this into Lovable:

Create a pricing page for my app that:
1. Shows the plan name, price, and benefits
2. Has a 'Buy Now' or 'Upgrade' button
3. Calls the create-checkout Edge Function with the Stripe price ID and current user's email
4. Redirects to the returned Stripe checkout URL
5. Shows a loading spinner while waiting
6. Shows an error message if checkout creation fails
7. If the user is not logged in, redirect them to sign up first
7

Create success and cancellation pages

Stripe redirects back to your app, but those pages still need to feel complete and useful. Add a success page that confirms what happened and a clear next step. Keep the cancellation page simple and return the user to pricing.

Paste this into Lovable:

Create two pages:
1. /payment-success
   - show a success state with a checkmark
   - explain what the user just purchased
   - if it is a subscription, say access may take a few seconds to activate
   - include a button to go to the dashboard
2. /pricing or /payment-cancelled
   - explain that no payment was completed
   - include a button to try checkout again
8

Create the Stripe webhook Edge Function

Checkout redirects are not enough. You must trust the webhook, not the browser, to confirm payment. This function should verify the Stripe signature and update your database based on the event type.

Paste this into Lovable:

Create a Supabase Edge Function called 'stripe-webhook' that:
1. Reads the raw request body with req.text()
2. Verifies the Stripe signature using STRIPE_WEBHOOK_SECRET
3. Handles checkout.session.completed
4. Handles customer.subscription.updated and customer.subscription.deleted if this is a subscription app
5. For one-time payments, insert or update the payments table
6. For subscriptions, insert or update the subscriptions table
7. Match users using metadata.user_id or customer email
8. Return status 200 quickly after processing
9. Log the event type for debugging
10. Add error handling for invalid signatures and missing users
9

Register the webhook in Stripe Dashboard

In Stripe Dashboard → Developers → Webhooks, add your Supabase function URL and select the right events. If you are building subscriptions, include the recurring lifecycle events. Then copy the webhook signing secret back into Supabase secrets.

# Endpoint URL
https://your-project.supabase.co/functions/v1/stripe-webhook

# Minimum events for one-time payments
checkout.session.completed
payment_intent.payment_failed

# Add these for subscriptions
customer.subscription.updated
customer.subscription.deleted
invoice.payment_failed
10

Test the full flow in Stripe test mode

Do not switch to live mode yet. Run the entire flow with Stripe test cards and confirm both the checkout redirect and the database update. This is the step most people skip, which is why they only discover broken webhooks after launch.

# Stripe test card
4242 4242 4242 4242
# Any future expiry date
# Any 3-digit CVC

# What to verify after payment:
# 1. Stripe shows a successful checkout
# 2. Your app redirects to /payment-success
# 3. Supabase payments/subscriptions table updates
# 4. The user gains access to the feature or plan
11

Add live mode keys only after the test flow works

Once test mode works end to end, duplicate the setup in live mode. Create the live product or price, add live secrets in Supabase, register the live webhook, and test with a small real charge. Keep test and live price IDs separate.

# Replace test secrets only after confirming the flow
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_live_...

# Also make sure your live Stripe price ID is not the same as the test price ID

Common errors

CORS error when calling Edge Function

Your frontend is being blocked from calling the Supabase Edge Function.

Fix: Add CORS headers to both the OPTIONS and POST responses in the create-checkout function.

Stripe key not found

The Edge Function can't find your STRIPE_SECRET_KEY.

Fix: Add the key in Supabase → Edge Functions → Secrets. Do not place server secrets only in frontend env variables.

Webhook signature verification failed

Stripe can't verify the webhook is legitimate.

Fix: Make sure STRIPE_WEBHOOK_SECRET exactly matches the signing secret shown for that webhook endpoint in Stripe Dashboard.

Checkout succeeds but access is not granted

The redirect worked, but your webhook failed or never updated the database.

Fix: Check Stripe webhook delivery logs first, then inspect your Supabase function logs. The webhook is the source of truth, not the success page.

User returns to the app but sees the wrong plan

Your database did not save the Stripe price ID or status correctly.

Fix: Store stripe_price_id, status, and Stripe customer/subscription IDs in the database so your UI can reliably determine what the user bought.

Related guides

Weekly Newsletter

Get next week's fix before you need it.

Join developers getting weekly vibe coding tips, error fixes, and tool updates.

Subscribe on Substack →