The Complete Stripe Payments Guide: One-Time Charges and Subscriptions (Next.js Practice)
Stripe is the default choice for SaaS and subscription products, but it comes with a lot of moving parts. Teams often mis-model subscriptions, pick the wrong integration, or rely on front-end success pages—then end up refactoring after launch.
This guide explains the Stripe essentials with a clear engineering lens: one-time payments vs subscriptions, key objects, webhook strategy, and a minimal database schema that scales.
1. Two layers to clarify: Integration vs Billing Model
Integration options
| Integration | What it is | Recommendation |
|---|---|---|
| Checkout | Stripe-hosted payment page | ⭐⭐⭐⭐⭐ |
| Payment Element / Elements | Embedded custom UI | ⭐⭐⭐ |
| Payment Links | No-code shareable links | ⭐⭐ |
| Invoicing | B2B invoice flow | ⭐⭐ |
Takeaway: For MVPs, Stripe Checkout is the fastest and safest path.
Billing models
| Model | Core Stripe objects |
|---|---|
| One-time payment | PaymentIntent |
| Monthly subscription | Subscription + Invoice |
| Annual subscription | Subscription + Invoice |
Monthly and annual subscriptions are the same system—just different Price.recurring.interval values.
2. One-time payments: simple, but traceable
Use cases: one-off purchases, credit packs, single-service fees.
Recommended Checkout flow:
- Create a Checkout Session (
mode=payment) on the server - Redirect to
session.url - Confirm payment via Webhook
- Update order status
Key objects:
Checkout Session(cs_...)PaymentIntent(pi_...)Charge(ch_...)
Rule: Webhooks are the source of truth. Do not rely on the front-end success page.
3. Subscriptions: separate price from subscription
Correct modeling in Stripe
- Product: your plan (e.g., Pro)
- Price: monthly or yearly pricing
- Subscription: a customer’s subscription record
Example:
pro_monthly→recurring.interval = monthpro_yearly→recurring.interval = year
Subscription flow (Checkout)
- Create Checkout Session (
mode=subscription) - Pass
line_items.price - User completes payment
- Stripe creates Subscription
- Invoice generated each billing period
- Webhooks drive state changes
4. One-time vs subscription: quick comparison
| Dimension | One-time | Subscription |
|---|---|---|
| Checkout mode | payment | subscription |
| Core objects | PaymentIntent | Subscription + Invoice |
| Auto-renew | No | Yes |
| Subscription table needed | No | Yes |
| Webhook complexity | Low | Medium |
5. Keys and environment variables
Secret Key (server)
STRIPE_SECRET_KEY=sk_...Used to create sessions, subscriptions, refunds, and handle webhooks.
Publishable Key (client)
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=pk_...Only needed when using Payment Element or Stripe.js directly. For Checkout redirects, it is usually unnecessary.
Webhook Secret (production)
STRIPE_WEBHOOK_SECRET=whsec_...Validates webhook signatures and prevents spoofed requests.
6. Minimal, scalable database design
Recommended tables:
| Table | Purpose |
|---|---|
| billing_orders | Business order entry point |
| billing_payments | Payment ledger and refunds |
| billing_subscriptions | Subscription state tracking |
| stripe_events | Webhook idempotency and audit |
Why split them?
- Orders, payments, and subscriptions are different lifecycles
- Subscriptions recur and can fail
- Webhooks are retried and must be idempotent
7. Webhooks are the source of truth
Front-end success ≠ payment success. Validate via webhook events:
One-time payment events:
checkout.session.completedpayment_intent.succeeded
Subscription events:
customer.subscription.createdcustomer.subscription.updatedinvoice.paidinvoice.payment_failed
Best practices:
- Store
event.idinstripe_eventsfor idempotency - Always retry on failures
- Keep logs for audit
8. Next.js implementation tips
- Use API Routes or Route Handlers to create Checkout Sessions
- Verify webhooks in a dedicated server endpoint
- Add a
billingservice layer to isolate Stripe logic - Drive order/subscription state with a clear state machine
9. Launch checklist
- Billing model clearly separated (one-time vs subscription)
- Stripe Checkout used for MVP
- Orders, payments, subscriptions modeled separately
- Webhooks are treated as truth
- Idempotency and retries implemented
If you later add trials, coupons, multi-currency, or invoicing, extend this model rather than replacing it.
WenHaoFree