Billing & Subscription Management
TanStack Ship billing system for Stripe subscription management, recurring payment processing, invoice generation, and customer self-service portal integration.
Overview
The billing and subscription management system in TanStack Ship provides a complete, production-ready solution for handling the full subscription lifecycle. Powered by Stripe integration, it manages everything from checkout and recurring billing to invoice generation, discount application, and customer self-service.
Whether your SaaS business offers monthly subscriptions, annual plans, or one-time purchases, the billing engine dynamically adapts to your pricing strategy. Webhook-driven state synchronization ensures payment events are never lost, and idempotent operations prevent duplicate processing.
As part of the platform, the billing module integrates with the credit system, campaign and offer management, UTM tracking, and revenue analytics dashboards.
Design Flow
The subscription lifecycle follows a well-defined flow from plan selection through recurring billing:
Design Philosophy
The billing system is built on three core design principles:
- Reliability First — Webhook-driven state synchronization ensures that every payment event is captured, even during transient network failures. Idempotent operations prevent duplicate processing when webhooks are retried.
- Zero-Trust Pricing — All price calculations happen server-side at checkout, not on the client. This prevents tampering and ensures consistent, auditable pricing logic across all users. Unlike Paddle or Chargebee — which operate as external billing platforms with redirect-based checkout flows — our billing system integrates Stripe Checkout directly into your application's routing, with pricing logic running in your own server environment for full control over discount composition, credit application, and UTM-based pricing.
- Composable Discount Architecture — Discounts from multiple sources (coupons, credits, UTM campaigns, holiday rates) are evaluated in a defined priority chain. Each source adds or subtracts independently, enabling complex promotional strategies without modifying core pricing logic.
Core Capabilities
Stripe Checkout Integration
The system uses Stripe Checkout Sessions for secure, PCI-compliant payment processing. When a user selects a plan, the server creates a Checkout Session with the appropriate pricing, customer metadata, and redirect URLs.
Payment Flow:
- A user selects a plan on your pricing page
- The server creates a Stripe Checkout Session with the chosen plan and any applicable discounts
- The user is securely redirected to Stripe's hosted checkout page
- After successful payment, Stripe redirects back to your application
- The subscription status is automatically synchronized via webhooks
- The user's dashboard immediately reflects the new plan
The checkout flow supports coupon codes, trial periods, and pre-filled customer information for returning users. Every transaction is processed on Stripe's PCI-compliant infrastructure, so no sensitive payment data ever touches your servers.
Example — Checkout API response:
{
"url": "https://checkout.stripe.com/c/pay/cs_test_...",
"sessionId": "cs_test_...",
"plan": "Pro Monthly",
"amount": 2900,
"currency": "usd",
"discounts": ["WELCOME20"]
}
Client-side pricing card example:
import { useMutation } from '@tanstack/react-query'
import { useNavigate } from '@tanstack/react-router'
export function PricingCard({ plan }: { plan: { id: string; name: string; price: number } }) {
const navigate = useNavigate()
const checkout = useMutation({
mutationFn: async () => {
const res = await fetch('/api/stripe/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId: plan.id }),
})
const data = await res.json() as { url: string }
window.location.href = data.url
},
})
return (
<div className="rounded-lg border p-6 text-center">
<h3 className="text-lg font-semibold">{plan.name}</h3>
<p className="mt-2 text-3xl font-bold">${plan.price}<span className="text-sm font-normal text-muted-foreground">/mo</span></p>
<button
className="mt-4 w-full rounded-md bg-primary py-2 text-white"
onClick={() => checkout.mutate()}
disabled={checkout.isPending}
>
{checkout.isPending ? 'Redirecting...' : 'Subscribe'}
</button>
</div>
)
}
Subscription Plan Management
Two subscription tiers are available out of the box, both fully configurable through the Stripe Dashboard:
- Monthly — Standard recurring billing with monthly cycles, ideal for users who prefer flexibility
- Annual — Discounted pricing with yearly billing, typically offering approximately 20% savings compared to monthly
You are not limited to these two options. Since plans are defined as Stripe Products with associated Prices, you can create any billing structure — quarterly, weekly, usage-based, or tiered pricing — all through the Stripe Dashboard without code changes.
The product catalog is cached server-side and exposed through a pricing endpoint. This endpoint performs real-time price calculation, dynamically factoring in active discounts, promotional offers, and credit balances. This means your pricing page always reflects the most up-to-date rates without manual synchronization.
Customer Self-Service Portal
Users can manage their subscription directly from your application through the Stripe Customer Portal, without ever needing to contact support. The portal provides:
- View current plan details and subscription status
- Upgrade or downgrade between available plans
- Update payment methods securely
- View complete invoice and payment history
- Cancel subscriptions (with optional exit survey)
The portal session is created server-side with secure authentication, then redirects the user to Stripe's hosted management interface. This means you get a professional billing management experience without building it yourself.
Dynamic Price Calculation
The pricing engine computes final prices dynamically during checkout, evaluating multiple discount sources in a defined priority order:
- Base plan price — The standard monthly or annual rate
- Discount offers — Percentage-based or fixed-amount coupons from the campaign system
- Credit deductions — Credit points applied at checkout as an alternative payment method
- UTM-based pricing — Traffic-source-specific promotions triggered by marketing campaign parameters
- Holiday rates — Seasonal promotional pricing for time-limited campaigns
This layered approach ensures that every user sees the correct final price based on their unique combination of applicable discounts. The calculation happens server-side at checkout, so pricing logic remains secure and consistent.
Example — Price calculation breakdown:
| Component | Amount |
|---|---|
| Base annual plan | $240 |
| Welcome discount (-20%) | -$48 |
| Credit applied | -$10 |
| Final price | $182 |
Invoice and Payment History
Every transaction is recorded and accessible from the user dashboard. The system tracks:
- Payment status: pending, completed, failed, refunded
- Associated plan details and billing period
- Applied discounts, credits, and promotional adjustments
- Payment intent IDs for financial reconciliation
This complete history provides an audit trail for accounting, compliance, and customer support. Users can view and download invoices at any time from their dashboard, reducing support requests for billing questions.
Best Practices
Webhook Reliability
Configure Stripe webhook retry settings in the Stripe Dashboard. The system handles duplicate webhook deliveries through idempotency checks, but enabling automatic retries (typically 3 attempts with exponential backoff) ensures zero revenue loss from transient network failures.
Subscription Reconciliation
Periodically reconcile Stripe subscription statuses against your database records. This catches edge cases where webhook deliveries might have been missed during brief outages. The subscription records include timestamps to facilitate this process.
Testing with Stripe Test Mode
Use Stripe's test mode during development with test card numbers:
4242 4242 4242 4242— Successful payment4000 0000 0000 0002— Declined card4000 0025 0000 3155— Requires authentication (3D Secure)
Run the billing seed script to populate sample products and prices for testing.
Handling Failed Payments
Configure Stripe's Smart Retries to automatically retry failed payments. The system tracks payment failures and can notify users whose subscriptions need payment method updates. Consider sending a reminder email before the subscription is eventually suspended.
Frequently Asked Questions
How do I set up Stripe for the first time? Configure your Stripe secret key, webhook secret, and publishable key in your environment configuration, then run the billing seed script. Detailed setup steps are provided in the Configuration section.
Can I create custom plans beyond monthly and annual? Yes. Plans are defined as Stripe Products with associated Prices. Create any pricing structure in the Stripe Dashboard — quarterly, weekly, or usage-based. The system automatically syncs new plans.
How does the system handle free trials? Stripe's trial period functionality is supported. When a user signs up for a plan with a trial, they receive immediate access while the first payment is deferred until the trial ends.
What happens when a payment fails? Stripe retries failed payments based on your configured retry schedule. If all retries are exhausted, the subscription is marked past_due and eventually canceled. The system tracks this status transition throughout.
Can users apply coupon codes during checkout? Yes. The checkout accepts an optional coupon parameter. Valid coupon codes from Stripe are applied to the Checkout Session before payment is processed.
Is multi-currency supported? Yes. Stripe supports multiple currencies. Define prices in different currencies in the Stripe Dashboard, and the catalog exposes all available pricing options.
How are refunds handled? Process refunds through the Stripe Dashboard. When a refund occurs, the system updates the transaction status automatically via webhook notifications.
Configuration
Configure these environment variables:
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
VITE_STRIPE_PUBLIC_KEY=pk_live_...
Create products and prices in the Stripe Dashboard, then run the seed script:
npm run db:seed:billing
For local development, use Stripe test mode keys. Test webhooks locally using the Stripe CLI:
stripe listen --forward-to localhost:3000/api/stripe/webhook
Integration Points
The billing system integrates with these platform modules:
- Campaigns and Offers — Discount codes are applied during checkout
- Credit System — Users can apply credit points toward subscriptions
- UTM Tracking — Traffic-source-specific pricing is evaluated at checkout
- Email Marketing — Transactional emails for receipts and subscription updates
- Revenue Analytics — Admin dashboard tracks MRR, ARR, and churn metrics
- User Management — Subscription status determines feature access permissions