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
1
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) |
2
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_)
3
Add price IDs to environment
4
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
Going to Production
1
Switch to live mode
Replace test API keys with live keys in production environment.
2
Create production products
Create new products in live mode (test products don’t transfer).
3
Add production webhook
Add endpoint in Stripe Dashboard pointing to production URL.
4
Update price IDs
Set production price IDs in environment variables.
5
Test with real card
Do a small real transaction to verify the flow.