← Back to list

stripe-integration
by jonit-dev
⭐ 0🍴 0📅 Jan 21, 2026
SKILL.md
name: stripe-integration description: Implement Stripe payment flows, webhooks, and billing logic. Use when working with payments, subscriptions, checkout, or credit systems.
Stripe Integration Skill
Comprehensive patterns for Stripe payment integration in this Next.js SaaS project.
Architecture Overview
server/stripe/ # Server-only Stripe instance
├── config.ts # Stripe SDK initialization
└── index.ts # Re-exports
shared/config/
├── stripe.ts # Price IDs, plan config, display helpers
├── subscription.config.ts # Single source of truth for plans
└── subscription.utils.ts # Helpers (resolvePriceId, getPlanByKey, etc.)
client/services/
└── stripeService.ts # Frontend API wrapper
app/api/
├── checkout/route.ts # Create checkout sessions
├── portal/route.ts # Customer portal sessions
├── subscription/ # Subscription management
│ ├── change/route.ts
│ ├── preview-change/route.ts
│ └── cancel-scheduled/route.ts
└── webhooks/stripe/ # Webhook handlers
├── route.ts
└── handlers/
└── subscription.handler.ts
Server-Side Stripe Client
// Import the singleton Stripe instance
import { stripe } from '@server/stripe';
// Example: Retrieve customer
const customer = await stripe.customers.retrieve(customerId);
// Example: Create checkout session
const session = await stripe.checkout.sessions.create({
customer: customerId,
line_items: [{ price: priceId, quantity: 1 }],
mode: 'subscription',
success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${baseUrl}/canceled`,
});
// Example: Retrieve subscription
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
// Example: Update subscription (plan change)
const updated = await stripe.subscriptions.update(subscriptionId, {
items: [{ id: itemId, price: newPriceId }],
proration_behavior: 'create_prorations',
});
Price Resolution (Single Source of Truth)
Always use the unified resolver functions:
import {
resolvePriceId,
resolvePlanOrPack,
assertKnownPriceId,
getPlanByPriceId,
getPlanByKey,
} from '@shared/config/stripe';
// Resolve any price ID to its metadata
const resolved = resolvePriceId(priceId);
// Returns: { type: 'plan' | 'pack', key, name, credits, ... } | null
// Assert price is valid (throws if unknown)
const plan = assertKnownPriceId(priceId);
// Get plan details by key (e.g., 'starter', 'pro')
const plan = getPlanByKey('pro');
// Returns: { key, name, creditsPerCycle, maxRollover, features, ... }
// Get display name for UI (adds " Plan" suffix for consistency)
import { getPlanDisplayName } from '@shared/config/stripe';
const displayName = getPlanDisplayName({ subscriptionTier: 'starter' });
// Returns: "Starter Plan"
Client-Side Service Usage
import { StripeService } from '@client/services/stripeService';
// Redirect to Stripe Checkout
await StripeService.redirectToCheckout(priceId, {
successUrl: '/success',
cancelUrl: '/pricing',
});
// Create checkout session (for embedded checkout)
const { url, sessionId, clientSecret } = await StripeService.createCheckoutSession(priceId, {
uiMode: 'embedded',
});
// Purchase credit pack
const { url } = await StripeService.purchaseCredits('medium');
// Redirect to Customer Portal
await StripeService.redirectToPortal();
// Preview subscription change (proration)
const preview = await StripeService.previewSubscriptionChange(targetPriceId);
// Execute subscription change
const result = await StripeService.changeSubscription(targetPriceId);
// Cancel subscription (at period end)
const result = await StripeService.cancelSubscription('reason');
// Get user profile with credits
const profile = await StripeService.getUserProfile();
// Check credit balance
const hasSufficient = await StripeService.hasSufficientCredits(5);
Subscription Configuration
Plans are defined in shared/config/subscription.config.ts:
// Current plan structure
{
key: 'pro', // Internal identifier (stored in DB)
name: 'Professional', // Display name
stripePriceId: 'price_xxx', // Stripe price ID
priceInCents: 4900, // $49.00
currency: 'usd',
interval: 'month',
creditsPerCycle: 1000,
maxRollover: 6000, // 6x monthly
rolloverMultiplier: 6,
features: [...],
recommended: true,
batchLimit: 50, // Max images in batch
}
Webhook Handling
Webhooks are processed in app/api/webhooks/stripe/route.ts:
// Key events handled:
// - checkout.session.completed → Add credits or create subscription
// - customer.subscription.updated → Update subscription tier, add credits for upgrades
// - customer.subscription.deleted → Mark subscription canceled
// - invoice.payment_succeeded → Add monthly credits on renewal
// - customer.subscription.trial_will_end → Send trial ending notification
// Subscription tier is stored as plan KEY (e.g., 'pro'), not name ('Professional')
await supabaseAdmin
.from('profiles')
.update({
subscription_status: subscription.status,
subscription_tier: planMetadata.key, // 'pro', not 'Professional'
})
.eq('id', userId);
Key Types
// From shared/types/stripe.types.ts
interface IUserProfile {
id: string;
stripe_customer_id: string | null;
subscription_credits_balance: number; // From subscription (expire at cycle end)
purchased_credits_balance: number; // From purchases (never expire)
subscription_status: SubscriptionStatus | null;
subscription_tier: string | null; // Plan key: 'starter', 'pro', etc.
role: 'user' | 'admin';
}
interface ISubscription {
id: string; // Stripe subscription ID
user_id: string;
status: string;
price_id: string;
current_period_start: string;
current_period_end: string;
cancel_at_period_end: boolean;
scheduled_price_id?: string; // For scheduled downgrades
scheduled_change_date?: string;
}
type SubscriptionStatus = 'active' | 'trialing' | 'past_due' | 'canceled' | 'unpaid';
Setup Scripts
Initial Stripe Setup
# Create products and prices in Stripe dashboard, then update subscription.config.ts
yarn stripe:setup # or scripts/stripe-setup.sh
Manual Price Creation (via API)
# The stripe-setup.sh script uses curl to create products/prices:
curl -X POST \
-u "$STRIPE_SECRET_KEY:" \
-d "name=Professional Plan" \
-d "description=For professionals - 1000 credits per month" \
"https://api.stripe.com/v1/products"
curl -X POST \
-u "$STRIPE_SECRET_KEY:" \
-d "product=prod_xxx" \
-d "currency=usd" \
-d "unit_amount=4900" \
-d "recurring[interval]=month" \
"https://api.stripe.com/v1/prices"
Common Patterns
Checking User's Plan
// From profile
const tier = user.profile?.subscription_tier; // 'starter', 'pro', etc.
// Display name
const displayName = getPlanDisplayName({ subscriptionTier: tier });
// Get plan config
const plan = getPlanByKey(tier);
const batchLimit = plan?.batchLimit ?? 1;
Credit Operations
// Check sufficient credits (RPC)
const { data } = await supabase.rpc('has_sufficient_credits', {
target_user_id: userId,
required_amount: 5,
});
// Deduct credits (RPC - handles dual pools)
const { data: newBalance } = await supabase.rpc('deduct_credits', {
target_user_id: userId,
amount: 5,
ref_id: jobId,
description: 'Image upscale',
});
// Add subscription credits (RPC)
await supabaseAdmin.rpc('add_subscription_credits', {
target_user_id: userId,
amount: 100,
ref_id: subscriptionId,
description: 'Monthly subscription credits',
});
Proration Preview
// Get proration cost before plan change
const preview = await stripe.invoices.retrieveUpcoming({
customer: customerId,
subscription: subscriptionId,
subscription_items: [{ id: itemId, price: newPriceId }],
subscription_proration_behavior: 'create_prorations',
});
Environment Variables
# .env.client (public)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxx
# .env.api (secrets)
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
Cloudflare Workers Compatibility
The Stripe client uses fetch-based HTTP client for edge compatibility:
// server/stripe/config.ts
export const stripe = new Stripe(serverEnv.STRIPE_SECRET_KEY, {
apiVersion: '2025-12-15.clover',
httpClient: Stripe.createFetchHttpClient(),
telemetry: false,
});
Testing
// Mock tokens for test mode
const token = 'test_token_mock_user_123';
// Test price IDs are accepted in test mode
// Configure ENV=test to enable mock responses
Score
Total Score
50/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon