How to Add Payments to Your SaaS (2026 Guide)
Payment integration is where most SaaS builders lose weeks. Not because it's hard, but because there are too many choices and too many edge cases nobody warns you about. This guide gets you from zero to collecting payments with the least pain.
Choose Your Approach
| Approach | Complexity | Tax handling | Best for |
|---|---|---|---|
| Stripe (direct) | Medium-High | You handle it | Full control, US/EU focused |
| Lemon Squeezy | Low | Handled (MoR) | Global sales, simple billing |
| Paddle | Low | Handled (MoR) | B2B SaaS, enterprise |
| Polar | Low | Handled (MoR) | Developer products |
Merchant of Record vs Direct Payment Processing
Direct (Stripe, PayPal): You're the seller. You handle sales tax, VAT, refunds, and compliance.
Merchant of Record (Lemon Squeezy, Paddle, Polar): They're the legal seller. They handle taxes, compliance, refunds. You get payouts.
Our recommendation: If you're selling globally and don't want to deal with tax compliance in 130+ countries, use a Merchant of Record. If you need full control and are US/EU-only, go direct with Stripe.
Option 1: Stripe (Direct Integration)
Setup Steps
Step 1: Create products and prices
// Create product + price in Stripe dashboard or via API
const product = await stripe.products.create({
name: 'Pro Plan',
description: 'Full access to all features',
});
const price = await stripe.prices.create({
product: product.id,
unit_amount: 2900, // $29.00
currency: 'usd',
recurring: { interval: 'month' },
});
Step 2: Create a Checkout Session
// When user clicks "Subscribe"
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: 'price_xxxxx', quantity: 1 }],
success_url: 'https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://yourapp.com/pricing',
customer_email: user.email,
metadata: { userId: user.id }, // Critical: link to your user
});
// Redirect user to session.url
Step 3: Handle webhooks (the important part)
// POST /api/webhooks/stripe
app.post('/api/webhooks/stripe', async (req, res) => {
const event = stripe.webhooks.constructEvent(
req.body,
req.headers['stripe-signature'],
process.env.STRIPE_WEBHOOK_SECRET
);
switch (event.type) {
case 'checkout.session.completed':
// Activate subscription for user
const session = event.data.object;
await db.users.update({
where: { id: session.metadata.userId },
data: {
stripeCustomerId: session.customer,
subscriptionId: session.subscription,
plan: 'pro',
subscriptionStatus: 'active',
},
});
break;
case 'invoice.payment_succeeded':
// Renewal succeeded — keep access active
break;
case 'invoice.payment_failed':
// Payment failed — notify user, maybe downgrade
break;
case 'customer.subscription.deleted':
// Subscription canceled — remove access
await db.users.update({
where: { stripeCustomerId: event.data.object.customer },
data: { plan: 'free', subscriptionStatus: 'canceled' },
});
break;
}
res.json({ received: true });
});
Step 4: Customer portal (self-service)
// Let users manage their subscription
const portalSession = await stripe.billingPortal.sessions.create({
customer: user.stripeCustomerId,
return_url: 'https://yourapp.com/settings',
});
// Redirect to portalSession.url
Stripe Tax Handling
If using Stripe directly, you need to handle sales tax. Options:
- Stripe Tax: Automatic tax calculation ($0.50/transaction). Easiest option.
- TaxJar/Avalara: Third-party tax calculation services.
- Manual: Only viable if selling exclusively in one jurisdiction.
Option 2: Lemon Squeezy (Merchant of Record)
Setup Steps
Step 1: Create products in Lemon Squeezy dashboard
Set up your plans, pricing, and trial periods in the dashboard. No code needed.
Step 2: Integrate checkout
// Embed checkout or redirect
const checkoutUrl = `https://yourstore.lemonsqueezy.com/checkout/buy/${variantId}?checkout[custom][user_id]=${userId}`;
// Or use the JavaScript SDK
LemonSqueezy.Setup({ apiKey: process.env.LS_API_KEY });
const checkout = await LemonSqueezy.createCheckout({
storeId: STORE_ID,
variantId: VARIANT_ID,
checkoutData: {
custom: { user_id: userId },
},
});
Step 3: Handle webhooks
// POST /api/webhooks/lemonsqueezy
app.post('/api/webhooks/lemonsqueezy', async (req, res) => {
// Verify webhook signature
const event = req.body;
switch (event.meta.event_name) {
case 'subscription_created':
await db.users.update({
where: { id: event.meta.custom_data.user_id },
data: {
plan: 'pro',
lsCustomerId: event.data.attributes.customer_id,
lsSubscriptionId: event.data.id,
subscriptionStatus: 'active',
},
});
break;
case 'subscription_cancelled':
// Access continues until period ends
break;
case 'subscription_expired':
await db.users.update({
where: { lsSubscriptionId: event.data.id },
data: { plan: 'free', subscriptionStatus: 'expired' },
});
break;
}
res.json({ received: true });
});
That's it. No tax configuration. No compliance worries. Lemon Squeezy handles everything as the merchant of record.
Pricing Models
Flat-Rate Monthly/Annual
The simplest model. $29/month or $290/year (give 2 months free for annual).
Free → Pro ($29/mo) → Enterprise (custom)
Best for: Most early-stage SaaS. Start here.
Per-Seat Pricing
Charge per user. Common for B2B tools.
$10/user/month, minimum 1 user
Implementation tip: Track seats in your database. Use Stripe's quantity parameter on subscriptions.
Usage-Based
Charge based on consumption (API calls, storage, compute).
$0.01/API call or $5/1000 credits
Implementation: Track usage in your app, report to Stripe via metered billing, or use Lemon Squeezy's usage-based plans.
Credits/Tokens
Sell prepaid credits that users consume.
100 credits = $10. Each action costs 1-10 credits.
Best for: AI products where cost per operation varies. Simplifies pricing psychology.
Common Mistakes
1. Not Handling Failed Payments
30-40% of subscription churn is "involuntary" — failed credit cards, expired cards, insufficient funds. Build a dunning flow:
- Day 0: Payment fails → email user
- Day 3: Retry payment → second email
- Day 7: Retry → warning: access will be restricted
- Day 14: Downgrade to free tier
Stripe handles retries automatically (Smart Retries), but you need to handle the user communication and access changes.
2. Not Testing Webhooks
Test webhooks thoroughly. Use Stripe CLI (stripe listen --forward-to localhost:3000/api/webhooks) during development. Test every event type:
- Successful payment
- Failed payment
- Subscription canceled
- Subscription upgraded/downgraded
- Refund issued
- Disputed charge
3. Not Linking Stripe Customer to Your User
Always pass your userId in checkout metadata. Without this link, you can't grant access when the webhook fires.
4. Checking Subscription Status Client-Side
Never check subscription status via client-side API calls. Store subscription status in your database, update via webhooks, and check your database.
5. Forgetting Annual Billing
Always offer annual billing at a discount (typically 2 months free). 30-40% of customers prefer annual. Annual billing reduces churn and improves cash flow.
6. No Free Tier or Trial
Don't ask users to pay before they experience value. Options:
- Free tier: Limited features, unlimited time
- Free trial: Full features, limited time (7 or 14 days)
- Freemium: Core product free, premium features paid
Security Checklist
- Stripe API keys in environment variables (never in code)
- Webhook signature verification on every webhook endpoint
- HTTPS everywhere (Stripe requires it)
- Server-side subscription checks (never trust client-side)
- PCI compliance via Stripe Checkout (don't handle cards directly)
- Idempotent webhook handling (webhooks can fire multiple times)
FAQ
Stripe or Lemon Squeezy?
Stripe if you want full control and are comfortable handling tax compliance. Lemon Squeezy if you want to ship fast and sell globally without worrying about taxes. Most indie hackers should start with Lemon Squeezy.
How much does payment processing cost?
Stripe: 2.9% + $0.30 per transaction. Lemon Squeezy: 5% + payment processing (~8% total). Paddle: 5% + processing. The MoR premium is the cost of tax compliance handled for you.
Should I build billing myself or use a library?
Use existing solutions. Libraries like @lemonsqueezy/lemonsqueezy.js, stripe npm package, or full billing stacks like Tier, Lago, or Orb for usage-based billing.
When should I add payments?
After you've validated that people want your product (sign-ups, waitlist, or usage of free tier). Don't build billing before you have users.
How do I handle refunds?
Process refunds through Stripe/Lemon Squeezy dashboard or API. Update user access accordingly via webhooks. Have a clear refund policy on your site.
The Bottom Line
For most SaaS builders in 2026:
- Start with Lemon Squeezy or Polar for simplicity and global tax handling
- Migrate to Stripe when you need more control or want lower fees at scale
- Keep billing simple — flat-rate monthly + annual discount
- Handle webhooks properly — this is where 90% of billing bugs live
- Test everything in test mode before going live
Ship payments in a weekend, not a month. The tools are good enough in 2026 that billing shouldn't be what slows you down.