stripe-stack
<quick_start> Add payments to a Next.js + Supabase project:
- Install Stripe:
npm install stripe @stripe/stripe-js - Add env vars (see quick_reference below)
- Create idempotency table (see schema below)
- Choose workflow:
setup-new-project.mdoradd-webhook-handler.md
// Lazy-loaded Stripe client
import Stripe from 'stripe';
let _stripe: Stripe | null = null;
export function getStripe(): Stripe {
if (!_stripe) {
_stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2025-12-15.clover' });
}
return _stripe;
}
</quick_start>
<success_criteria> Integration is successful when:
- Webhook handler uses database-backed idempotency (not in-memory)
- All keys in environment variables (never hardcoded)
- Test mode fully working before any live mode deployment
- Signature verification on all webhook endpoints
- Event logging before processing (insert-before-process pattern)
- Go-live checklist completed before production deployment </success_criteria>
<essential_principles>
Core Principles
-
Idempotency is Non-Negotiable
- ALL webhook handlers MUST use database-backed idempotency
- Never use in-memory Sets (lost on serverless cold starts)
- Insert event record BEFORE processing, not after
-
Test/Live Mode Separation
- Use environment variables for ALL keys (never hardcode)
- Test keys:
sk_test_,pk_test_,whsec_test_ - Live keys:
sk_live_,pk_live_,whsec_live_ - Products/prices must be recreated in live mode
-
Shared Stripe Account
- All NetZero Suite projects share ONE Stripe account
- Same webhook secret can be used across projects
- Each project has its own webhook endpoint URL
-
Lazy Client Initialization
- Never initialize Stripe at module level (build errors)
- Use factory function pattern for server-side client
- Check for API key before creating instance
</essential_principles>
What Are You Building?
Before proceeding, identify your use case:
| Use Case | Workflow | Description |
|---|---|---|
| New project | setup-new-project.md |
Fresh Stripe integration from scratch |
| Add webhooks | add-webhook-handler.md |
Add webhook handler to existing project |
| Subscriptions | implement-subscriptions.md |
Recurring billing with plans |
| Credit system | add-credit-system.md |
Pay-as-you-go credits |
| Go live | go-live-checklist.md |
Test → Production migration |
Workflow Routing
If setting up Stripe in a new project:
→ Read workflows/setup-new-project.md
→ Then read reference/environment-vars.md
→ Use templates/stripe-client.ts and templates/env-example.txt
If adding webhook handling:
→ Read workflows/add-webhook-handler.md
→ Then read reference/webhook-patterns.md
→ Use templates/webhook-handler-nextjs.ts and templates/idempotency-migration.sql
If implementing subscription billing:
→ Read workflows/implement-subscriptions.md
→ Then read reference/pricing-models.md
→ Use templates/plans-config.ts
If adding credit/usage-based system:
→ Read workflows/add-credit-system.md
→ Then read reference/pricing-models.md
If migrating test → production:
→ Read workflows/go-live-checklist.md
<quick_reference>
Quick Reference
Environment Variables (Standard)
# Server-side (never expose to client)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Client-side (safe to expose)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
# Optional: Price IDs (for test→live switching)
STRIPE_PRICE_STARTER_MONTHLY=price_...
STRIPE_PRICE_PRO_MONTHLY=price_...
Common Webhook Events
| Event | When It Fires | Action |
|---|---|---|
checkout.session.completed |
Customer completes checkout | Create subscription record |
customer.subscription.created |
New subscription starts | Initialize user limits |
customer.subscription.updated |
Plan change, renewal | Update plan/limits |
customer.subscription.deleted |
Cancellation | Downgrade to free |
invoice.paid |
Monthly renewal success | Reset usage counters |
invoice.payment_failed |
Payment failed | Mark as past_due |
Stripe Client Pattern
let _stripe: Stripe | null = null;
export function getStripe(): Stripe {
if (!_stripe) {
const key = process.env.STRIPE_SECRET_KEY;
if (!key) throw new Error('STRIPE_SECRET_KEY not configured');
_stripe = new Stripe(key, {
apiVersion: '2025-12-15.clover',
typescript: true
});
}
return _stripe;
}
Idempotency Table Schema
CREATE TABLE stripe_webhook_events (
id TEXT PRIMARY KEY, -- Use Stripe event ID directly
type TEXT NOT NULL, -- Event type
data JSONB NOT NULL, -- Full event payload
processed_at TIMESTAMPTZ DEFAULT NOW()
);
Webhook Handler Structure
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get('stripe-signature');
// 1. Verify signature
const event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
// 2. Check idempotency (BEFORE processing)
const { data: existing } = await supabase
.from('stripe_webhook_events')
.select('id')
.eq('id', event.id)
.single();
if (existing) return NextResponse.json({ duplicate: true });
// 3. Log event (INSERT before processing)
await supabase.from('stripe_webhook_events').insert({
id: event.id,
type: event.type,
data: event,
});
// 4. Process event
switch (event.type) {
case 'checkout.session.completed':
await handleCheckout(event.data.object);
break;
// ... other handlers
}
return NextResponse.json({ received: true });
}
</quick_reference>
<integration_notes>
Integration Notes
Works With
- Supabase: Use service role client for webhook handlers (bypasses RLS)
- Prisma: Alternative to Supabase for idempotency table
- Vercel: Add runtime/maxDuration config for webhook routes
- Next.js App Router: Use
request.text()for raw body
Related Skills
supabase-sql-skill- For database migrationscreate-hooks-skill- For post-deployment notifications
GitHub Repository
Private templates and examples available at:
github.com/ScientiaCapital/stripe-stack
</integration_notes>
<reference_index>
Reference Files
| File | Purpose |
|---|---|
reference/webhook-patterns.md |
Idempotency, event handling, error recovery |
reference/pricing-models.md |
Plans vs Credits vs Usage-based billing |
reference/environment-vars.md |
Standard env var conventions |
reference/common-errors.md |
Troubleshooting guide |
Template Files
| File | Purpose |
|---|---|
templates/webhook-handler-nextjs.ts |
Complete webhook route (copy-paste) |
templates/stripe-client.ts |
Lazy-loaded client factory |
templates/plans-config.ts |
Subscription plan definitions |
templates/idempotency-migration.sql |
Supabase migration |
templates/webhook-handler.test.ts |
Test template |
templates/env-example.txt |
Standard .env template |
Workflow Files
| File | Purpose |
|---|---|
workflows/setup-new-project.md |
Fresh Stripe integration |
workflows/add-webhook-handler.md |
Add webhook to existing project |
workflows/implement-subscriptions.md |
Subscription billing |
workflows/add-credit-system.md |
Pay-as-you-go credits |
workflows/go-live-checklist.md |
Test → Production migration |
</reference_index>
Emit Outcome Sidecar
As the final step, write to ~/.claude/skill-analytics/last-outcome-stripe-stack.json:
{"ts":"[UTC ISO8601]","skill":"stripe-stack","version":"1.0.0","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"webhooks_configured":[n],"products_created":[n],"checkout_flows_built":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}
Use status "partial" if some stages failed but results were produced. Use "error" only if no output was generated.
More from scientiacapital/skills
trading-signals
Expert trading partner for Options, Stocks, Crypto, Commodities, Gold, Silver, Oil, VIX, and Forex. Covers technical analysis (Elliott Wave, Wyckoff, Fibonacci, Markov Regime, Turtle), options strategies (25+ including iron condors, credit spreads, naked puts, PMCC, gamma scalping), Greeks analysis, risk management, and sentiment signals. Use when analyzing any market, designing options trades, evaluating positions, calculating Greeks, discussing trading strategies, or asking about price action on any asset class. Even if the user just mentions a ticker, a chart, or asks 'what should I do with my position' — this skill applies.
356business-model-canvas
Business model design using Alexander Osterwalder's 9 building blocks. Use when: business model, canvas, value proposition, customer segments, revenue streams, startup planning, analyze business, business strategy.
301data-analysis
Executive-grade data analysis with pandas/polars and McKinsey-quality visualizations. Use when analyzing data, building dashboards, creating investor presentations, or calculating SaaS metrics.
190content-marketing
B2B content marketing - thought leadership, SEO, LinkedIn, blog posts, case studies, and video scripts. Use when creating content strategy, writing posts, or building demand gen assets.
185crm-integration
CRM integration patterns for Close CRM, HubSpot, and Salesforce. Use when: Close CRM, HubSpot, Salesforce, CRM API, lead sync, deal sync, activity logging, CRM webhook, pipeline automation, contact enrichment.
176gtm-pricing
B2B go-to-market strategy, pricing models, ICP development, positioning, and competitive intelligence. Use when planning GTM strategy, setting pricing, defining ICP, or evaluating opportunities.
90