Payments
KwikSaaS uses Stripe for all payment processing. This guide covers setup, configuration, and testing.Payments are pre-configured. This guide helps you customize plans and connect
your Stripe account.
What’s Included
- Stripe Checkout — Hosted payment page for secure transactions
- Customer Portal — Self-service subscription management
- Webhook sync — Automatic database updates on payment events
- Plan gating — Feature access based on subscription status
- Multiple pricing — Subscriptions (monthly/yearly) and lifetime one-time purchases
Payment Flow
Prerequisites
Stripe Account
Create at stripe.com. Use test mode for development.
Supabase Database
Migrations applied with billing tables.
Stripe Setup
Get API keys
Go to Stripe Dashboard → Developers → API keys:
| Key | Environment Variable | Usage |
|---|---|---|
| Publishable key | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Client-side (safe to expose) |
| Secret key | STRIPE_SECRET_KEY | Server-side (keep secret) |
Create products
Go to Products in Stripe Dashboard:
- Click Add product
- Set name (e.g., “KwikSaaS Standard”)
- Add description
- Set pricing:
- One-time for lifetime access
- Recurring for subscriptions
- Save and copy the Price ID (starts with
price_)
Set up webhooks
For local development:Copy the webhook signing secret (For production:
whsec_...) to:- Go to Developers → Webhooks in Stripe Dashboard
- Add endpoint:
https://yourdomain.com/api/webhooks/stripe - Select events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failedpayment_intent.succeeded
- Copy the signing secret to production environment
Plan Configuration
Plans are defined insrc/lib/payments/plans.ts:
Plan Properties
| Property | Description |
|---|---|
id | Unique identifier (used in database) |
name | Display name |
monthlyPrice | Price shown on pricing page |
stripePriceIds | Stripe Price IDs for checkout |
features | Display features (marketing) |
featureAccess | Feature keys for access control |
allowPromotionCodes | Enable promo codes in checkout |
API Endpoints
Create Checkout Session
POST/api/checkout_sessions
Customer Portal
POST/api/customer_portal
Opens Stripe’s billing portal for subscription management.
Webhooks
POST/api/webhooks/stripe
Handles Stripe events and updates database:
| Event | Action |
|---|---|
checkout.session.completed | Create subscription or one-time purchase |
customer.subscription.created | Insert subscription record |
customer.subscription.updated | Update status, period dates |
customer.subscription.deleted | Mark as canceled |
invoice.payment_succeeded | Log payment history |
invoice.payment_failed | Log failure, update status |
payment_intent.succeeded | Record one-time purchase |
Database Tables
user_subscriptions
Stores active subscription state:
| Column | Description |
|---|---|
user_id | Supabase Auth user ID |
stripe_customer_id | Stripe Customer ID |
stripe_subscription_id | Stripe Subscription ID |
stripe_price_id | Current price ID |
status | active, trialing, canceled, past_due |
billing_cycle | monthly or yearly |
current_period_end | When current period ends |
cancel_at_period_end | If scheduled for cancellation |
one_time_purchases
Stores lifetime/one-time purchases:
| Column | Description |
|---|---|
user_id | Supabase Auth user ID |
stripe_payment_intent_id | Stripe Payment Intent ID |
status | succeeded, failed |
granted_at | When access was granted |
revoked_at | If access was revoked |
payment_history
Audit log of all payments:
| Column | Description |
|---|---|
user_id | Supabase Auth user ID |
amount | Payment amount (cents) |
currency | Currency code |
payment_type | recurring or one_time |
invoice_url | Link to Stripe invoice |
Access Control
Check user access with helpers insrc/lib/access.ts:
Feature Keys
| Key | Description |
|---|---|
basic | Basic app access (all plans) |
templates | Template library |
support | Priority support |
api | API access |
analytics | Advanced analytics |
custom_domain | Custom domain support |
advanced | Subscriber-only features |
Testing
Test Cards
Use these card numbers in Stripe test mode:| Scenario | Card Number |
|---|---|
| Successful payment | 4242 4242 4242 4242 |
| Declined card | 4000 0000 0000 0002 |
| Requires 3D Secure | 4000 0027 6000 3184 |
| Insufficient funds | 4000 0000 0000 9995 |
Testing Webhooks Locally
- Start your dev server:
npm run dev - Start Stripe listener:
- Complete a test checkout
- Check database for new records
Verify Webhook Events
Promo Codes & Coupons
Create in Stripe
- Go to Products → Coupons in Stripe Dashboard
- Create coupon with:
- Percentage or fixed amount off
- Duration (once, repeating, forever)
- Redemption limits
Enable in Plans
Troubleshooting
Checkout returns 400 error
Checkout returns 400 error
Check:
- Price ID is correct and matches Stripe
- Plan exists in
plans.tswith matching price ID - Stripe is in correct mode (test vs live)
Webhook signature verification failed
Webhook signature verification failed
Check: -
STRIPE_WEBHOOK_SECRET matches the endpoint - For local: restart
stripe listen and update secret - For production: verify webhook URL is
correctSubscription not appearing in database
Subscription not appearing in database
Check: 1. Webhook listener is running 2. Check Stripe Dashboard →
Developers → Webhooks for failures 3. Verify database migrations have been
applied 4. Check server logs for errors
Customer portal not working
Customer portal not working
Check:
- User has a
stripe_customer_idinuser_subscriptions - User is authenticated
- Portal is enabled in Stripe Dashboard settings