Stripe Setup — Complete Guide
Configure Stripe payment processing, subscriptions, and billing portal for HeartCo.
Overview
HeartCo integrates Stripe for:
- Subscriptions (monthly/annual billing)
- One-time charges (usage-based, add-ons)
- Customer Portal (self-service billing)
- Webhooks (payment events, subscription updates)
1. Create Products & Prices
Step 1: Create Subscription Products
In Stripe Dashboard:
- Click New Product
- Enter Product Name:
HeartCo Starter - Choose Recurring → Billing Period: Monthly
- Enter Price: 29.00 USD
- Click Create
Repeat for all plans:
| Plan | Monthly | Annual | Billing Period |
|---|---|---|---|
| Starter | $29 | $290 | Monthly + Annual |
| Pro | $79 | $790 | Monthly + Annual |
| Business | $199 | $1990 | Monthly + Annual |
| Enterprise | Custom | Custom | Custom |
Step 2: Add Annual Price to Monthly Products
For each product (e.g., "HeartCo Starter"):
- Open product → Pricing tab
- Click Add price
- Set Billing period: Annually
- Enter Price: 290.00 USD
- Click Create
Step 3: Copy Price IDs
For each plan, note the Price ID:
// Example format: price_1234567890abcdef
Starter Monthly: price_1234567890111111
Starter Annual: price_1234567890111112
Pro Monthly: price_1234567890222221
Pro Annual: price_1234567890222222
Update .env.local:
NEXT_PUBLIC_STRIPE_PRICE_STARTER_MONTHLY=price_1234567890111111
NEXT_PUBLIC_STRIPE_PRICE_STARTER_ANNUAL=price_1234567890111112
NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY=price_1234567890222221
NEXT_PUBLIC_STRIPE_PRICE_PRO_ANNUAL=price_1234567890222222
# ... continue for all plans2. Configure Webhooks
Step 1: Create Webhook Endpoint
In Stripe Dashboard Developers > Webhooks:
-
Click Add endpoint
-
Endpoint URL:
https://yourdomain.com/api/stripe/webhook- (Use
http://localhost:3000/api/stripe/webhookfor local testing)
- (Use
-
Events to send:
checkout.session.completed— subscription startedinvoice.paid— recurring payment succeededinvoice.payment_failed— payment declined/failedcustomer.subscription.updated— plan/billing changedcustomer.subscription.deleted— subscription cancelled
-
Click Create
Step 2: Get Webhook Secret
After creating the endpoint:
- Click the endpoint to view details
- Copy Signing secret (starts with
whsec_) - Add to Vercel environment:
STRIPE_WEBHOOK_SECRET=whsec_1234567890...Step 3: Test Webhook Locally (Optional)
Install Stripe CLI:
# macOS
brew install stripe/stripe-cli/stripe
# Or download from https://stripe.com/docs/stripe-cliStart webhook listener:
stripe listen --forward-to http://localhost:3000/api/stripe/webhookCopy the signing secret and add to .env.local:
STRIPE_WEBHOOK_SECRET=whsec_test_...3. Get API Keys
Step 1: Retrieve Keys
In Stripe Dashboard Settings > API keys:
- Copy Secret key (starts with
sk_live_orsk_test_) - Copy Publishable key (starts with
pk_live_orpk_test_)
Step 2: Add to Environment
# Server-side (keep secret)
STRIPE_SECRET_KEY=sk_live_1234567890...
# Client-side (public)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_1234567890...Step 3: Test vs. Live
During development:
- Use Test keys (dashboard shows "Viewing test data")
- Use test cards:
4242 4242 4242 4242(success),4000 0000 0000 0002(decline)
Before launch:
- Switch to Live keys in Stripe Dashboard
- Update
.env.productionin Vercel
4. Configure Customer Portal
Step 1: Enable Self-Service
In Stripe Dashboard Settings > Billing portal settings:
- Click Customize
- Under Features, enable:
- Switch plans — customers can upgrade/downgrade
- Cancel subscriptions — customers can cancel anytime
- Update payment methods — add/change credit cards
- Download invoices — access invoice history
Step 2: Set Return URL
-
Allowed return URLs: Add
https://yourdomain.com/dashboard/billing -
Click Save
Users can now access the portal via:
https://billing.stripe.com/b/login/{YOUR_CUSTOMER_ID}
Or by implementing a server action:
// src/app/api/stripe/portal/route.ts
import { stripe } from '~/lib/stripe';
export async function POST(req: Request) {
const { customerId } = await req.json();
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: 'https://yourdomain.com/dashboard/billing',
});
return Response.json({ url: session.url });
}5. Test Stripe Integration
Test Card Numbers
Use these in test mode (sk_test_...):
| Card | Result | CVC | Date |
|---|---|---|---|
| 4242 4242 4242 4242 | Success | Any | Any future date |
| 4000 0000 0000 0002 | Decline | Any | Any future date |
| 4000 0025 0000 3155 | 3D Secure required | Any | Any future date |
| 5555 5555 5555 4444 | Success (Mastercard) | Any | Any future date |
Simulate Payment Events
Trigger webhooks manually in Stripe Dashboard:
- Developers > Webhooks → click endpoint
- Sending events
- Select event type (e.g.,
checkout.session.completed) - Click Send test webhook
Monitor webhook delivery under Event log.
End-to-End Test Flow
- Start dev server:
pnpm dev - Go to pricing page
/pricing - Click "Get Started" on Starter plan
- Stripe Checkout opens → enter test card
- Complete checkout → redirect to success page
- Check Stripe Dashboard → new subscription created
- Check database → subscription record created
- Customer can access billing portal
6. Production Checklist
Before deploying to production:
- Switch Stripe Dashboard to Live mode (top right toggle)
- Update
.env.productionin Vercel with Live keys - Update webhook endpoint URL to production domain
- Re-create webhook with production signing secret
- Test full payment flow with real card ($1 test charge, auto-refunded)
- Verify webhook delivery in Stripe Dashboard
- Configure email notifications for failed payments
- Set up dunning rules (automated retry for failed payments)
- Review billing portal settings
- Test subscription cancellation and reactivation
7. Advanced Configuration
Dunning & Failed Payments
Enable automatic retry for failed payments:
- Settings > Billing settings > Dunning
- Configure retry schedule:
- Day 1, 3, 5, 7 (default)
- Email templates for soft declines
Usage-Based Billing
For add-on charges (e.g., API overage):
// src/server/api/routers/billing.ts
const { stripe } = require('~/lib/stripe');
export const billingRouter = createTRPCRouter({
reportUsage: staffProcedure
.input(z.object({ subscriptionItemId: z.string(), quantity: z.number() }))
.mutation(async ({ input }) => {
await stripe.subscriptionItems.createUsageRecord(
input.subscriptionItemId,
{ quantity: input.quantity }
);
return { success: true };
}),
});Tax Calculation (Optional)
Enable Stripe Tax for automatic tax calculation:
- Settings > Tax → Enable
- Configure tax rates by jurisdiction
- Stripe automatically calculates tax on checkout
Troubleshooting
Webhook Not Firing
- Check webhook endpoint URL is accessible (not localhost)
- Verify
STRIPE_WEBHOOK_SECRETmatches Stripe Dashboard - Check webhook Event log in Stripe Dashboard for errors
- Test with Stripe CLI locally first
Checkout Redirect Issues
- Verify success/cancel URLs in checkout session creation
- Check Stripe API keys are correct
- Ensure domain is not restricted in Stripe settings
Customer Not Found Error
- Verify customer is created in Stripe before creating subscription
- Check
customerIdmatches Stripe customer ID (not local ID) - Sync customers from Stripe if out of sync
Payment Method Declined
- Test with valid test cards (see Step 5)
- Check 3D Secure flow (some cards require additional authentication)
- Verify card expiration and CVC in test
Support
- Stripe Docs: stripe.com/docs
- Stripe API Reference: stripe.com/docs/api
- Stripe CLI: stripe.com/docs/stripe-cli
- Customer Portal: stripe.com/docs/billing/subscriptions/billing-portal