🎯

stripe-integration

🎯Skill

from dadbodgeoff/drift

VibeIndex|
What it does

Enables seamless Stripe payment integration with subscriptions, webhooks, and customer portal for SaaS applications.

πŸ“¦

Part of

dadbodgeoff/drift(69 items)

stripe-integration

Installation

npm installInstall npm package
npm install -g driftdetect
npm installInstall npm package
npm install -g driftdetect@latest
npm installInstall npm package
npm install -g driftdetect-mcp
Claude Desktop ConfigurationAdd this to your claude_desktop_config.json
{ "mcpServers": { "drift": { "command": "driftdetect-mcp" } } ...
πŸ“– Extracted from docs: dadbodgeoff/drift
4Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Complete Stripe payments integration with subscriptions, webhooks, and customer portal. Use when adding billing to a SaaS application with subscription tiers, usage-based pricing, or one-time payments.

Overview

# Stripe Integration

Production-ready Stripe integration for SaaS billing.

When to Use This Skill

  • Adding subscription billing to your SaaS
  • Implementing usage-based pricing
  • Setting up customer self-service portal
  • Handling payment webhooks securely

Architecture Overview

```

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ Frontend │────▢│ Backend │────▢│ Stripe β”‚

β”‚ Checkout β”‚ β”‚ Webhooks │◀────│ Events β”‚

β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”‚

β–Ό

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ Database β”‚

β”‚ (sync'd) β”‚

β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

```

Environment Setup

```bash

# .env

STRIPE_SECRET_KEY=sk_test_...

STRIPE_PUBLISHABLE_KEY=pk_test_...

STRIPE_WEBHOOK_SECRET=whsec_...

STRIPE_PRICE_FREE=price_...

STRIPE_PRICE_PRO=price_...

STRIPE_PRICE_ENTERPRISE=price_...

```

TypeScript Implementation

Stripe Client Setup

```typescript

// lib/stripe.ts

import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {

apiVersion: '2023-10-16',

typescript: true,

});

export const PRICES = {

free: process.env.STRIPE_PRICE_FREE!,

pro: process.env.STRIPE_PRICE_PRO!,

enterprise: process.env.STRIPE_PRICE_ENTERPRISE!,

} as const;

export type PriceTier = keyof typeof PRICES;

```

Create Checkout Session

```typescript

// api/create-checkout.ts

import { stripe, PRICES, PriceTier } from '@/lib/stripe';

interface CreateCheckoutParams {

userId: string;

email: string;

tier: PriceTier;

successUrl: string;

cancelUrl: string;

}

export async function createCheckoutSession({

userId,

email,

tier,

successUrl,

cancelUrl,

}: CreateCheckoutParams): Promise {

// Get or create Stripe customer

let customer = await getStripeCustomer(userId);

if (!customer) {

customer = await stripe.customers.create({

email,

metadata: { userId },

});

await saveStripeCustomerId(userId, customer.id);

}

const session = await stripe.checkout.sessions.create({

customer: customer.id,

mode: 'subscription',

payment_method_types: ['card'],

line_items: [

{

price: PRICES[tier],

quantity: 1,

},

],

success_url: ${successUrl}?session_id={CHECKOUT_SESSION_ID},

cancel_url: cancelUrl,

subscription_data: {

metadata: { userId },

},

allow_promotion_codes: true,

});

return session.url!;

}

```

Webhook Handler

```typescript

// api/webhooks/stripe.ts

import { stripe } from '@/lib/stripe';

import { headers } from 'next/headers';

const WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(req: Request) {

const body = await req.text();

const signature = headers().get('stripe-signature')!;

let event: Stripe.Event;

try {

event = stripe.webhooks.constructEvent(body, signature, WEBHOOK_SECRET);

} catch (err) {

console.error('Webhook signature verification failed:', err);

return new Response('Invalid signature', { status: 400 });

}

try {

switch (event.type) {

case 'checkout.session.completed':

await handleCheckoutComplete(event.data.object);

break;

case 'customer.subscription.updated':

await handleSubscriptionUpdate(event.data.object);

break;

case 'customer.subscription.deleted':

await handleSubscriptionCanceled(event.data.object);

break;

case 'invoice.payment_failed':

await handlePaymentFailed(event.data.object);

break;

default:

console.log(Unhandled event type: ${event.type});

}

return new Response('OK', { status: 200 });

} catch (err) {

console.error('Webhook handler error:', err);

return new Response('Webhook handler failed', { status: 500 });

}

}

async function handleCheckoutComplete(session: Stripe.Checkout.Session) {

const userId = session.metadata?.userId;

const subscriptionId = session.subscription as string;

if (!userId || !subscriptionId) return;

const subscription = await stripe.subscriptions.retrieve(subscriptionId);

const priceId = subscription.items.data[0]?.price.id;

const tier = Object.entries(PRICES).find(([, id]) => id === priceId)?.[0] || 'free';

await updateUserSubscription(userId, {

stripeSubscriptionId: subscriptionId,

stripeCustomerId: session.customer as string,

tier,

status: subscription.status,

currentPeriodEnd: new Date(subscription.current_period_end * 1000),

});

}

async function handleSubscriptionUpdate(subscription: Stripe.Subscription) {

const userId = subscription.metadata?.userId;

if (!userId) return;

const priceId = subscription.items.data[0]?.price.id;

const tier = Object.entries(PRICES).find(([, id]) => id === priceId)?.[0] || 'free';

await updateUserSubscription(userId, {

tier,

status: subscription.status,

currentPeriodEnd: new Date(subscription.current_period_end * 1000),

cancelAtPeriodEnd: subscription.cancel_at_period_end,

});

}

async function handleSubscriptionCanceled(subscription: Stripe.Subscription) {

const userId = subscription.metadata?.userId;

if (!userId) return;

await updateUserSubscription(userId, {

tier: 'free',

status: 'canceled',

stripeSubscriptionId: null,

});

}

async function handlePaymentFailed(invoice: Stripe.Invoice) {

const customerId = invoice.customer as string;

const user = await getUserByStripeCustomerId(customerId);

if (user) {

await sendPaymentFailedEmail(user.email, {

amount: invoice.amount_due / 100,

nextRetry: invoice.next_payment_attempt

? new Date(invoice.next_payment_attempt * 1000)

: null,

});

}

}

```

Customer Portal

```typescript

// api/create-portal.ts

export async function createPortalSession(userId: string): Promise {

const user = await getUser(userId);

if (!user?.stripeCustomerId) {

throw new Error('No Stripe customer found');

}

const session = await stripe.billingPortal.sessions.create({

customer: user.stripeCustomerId,

return_url: ${process.env.NEXT_PUBLIC_URL}/settings/billing,

});

return session.url;

}

```

Python Implementation

FastAPI Webhook Handler

```python

# webhooks/stripe.py

import stripe

from fastapi import APIRouter, Request, HTTPException, Header

router = APIRouter()

stripe.api_key = settings.STRIPE_SECRET_KEY

@router.post("/webhooks/stripe")

async def stripe_webhook(

request: Request,

stripe_signature: str = Header(None),

):

payload = await request.body()

try:

event = stripe.Webhook.construct_event(

payload,

stripe_signature,

settings.STRIPE_WEBHOOK_SECRET,

)

except stripe.error.SignatureVerificationError:

raise HTTPException(status_code=400, detail="Invalid signature")

handlers = {

"checkout.session.completed": handle_checkout_complete,

"customer.subscription.updated": handle_subscription_update,

"customer.subscription.deleted": handle_subscription_canceled,

"invoice.payment_failed": handle_payment_failed,

}

handler = handlers.get(event["type"])

if handler:

await handler(event["data"]["object"])

return {"status": "ok"}

async def handle_checkout_complete(session: dict):

user_id = session.get("metadata", {}).get("userId")

subscription_id = session.get("subscription")

if not user_id or not subscription_id:

return

subscription = stripe.Subscription.retrieve(subscription_id)

price_id = subscription["items"]["data"][0]["price"]["id"]

tier = PRICE_TO_TIER.get(price_id, "free")

await update_user_subscription(

user_id=user_id,

stripe_subscription_id=subscription_id,

stripe_customer_id=session["customer"],

tier=tier,

status=subscription["status"],

current_period_end=datetime.fromtimestamp(

subscription["current_period_end"]

),

)

```

Frontend Checkout Button

```tsx

// components/CheckoutButton.tsx

'use client';

import { useState } from 'react';

interface CheckoutButtonProps {

tier: 'pro' | 'enterprise';

children: React.ReactNode;

}

export function CheckoutButton({ tier, children }: CheckoutButtonProps) {

const [loading, setLoading] = useState(false);

const handleCheckout = async () => {

setLoading(true);

try {

const response = await fetch('/api/create-checkout', {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({ tier }),

});

const { url } = await response.json();

window.location.href = url;

} catch (error) {

console.error('Checkout error:', error);

setLoading(false);

}

};

return (

);

}

```

Database Schema

```sql

-- users table additions

ALTER TABLE users ADD COLUMN stripe_customer_id TEXT;

ALTER TABLE users ADD COLUMN stripe_subscription_id TEXT;

ALTER TABLE users ADD COLUMN subscription_tier TEXT DEFAULT 'free';

ALTER TABLE users ADD COLUMN subscription_status TEXT DEFAULT 'active';

ALTER TABLE users ADD COLUMN current_period_end TIMESTAMP;

ALTER TABLE users ADD COLUMN cancel_at_period_end BOOLEAN DEFAULT FALSE;

CREATE INDEX idx_users_stripe_customer ON users(stripe_customer_id);

```

Testing Webhooks Locally

```bash

# Install Stripe CLI

brew install stripe/stripe-cli/stripe

# Login

stripe login

# Forward webhooks to local server

stripe listen --forward-to localhost:3000/api/webhooks/stripe

# Trigger test events

stripe trigger checkout.session.completed

stripe trigger customer.subscription.updated

```

Best Practices

  1. Always verify webhook signatures: Never trust unverified payloads
  2. Make webhooks idempotent: Same event may be delivered multiple times
  3. Store Stripe IDs: Keep customer/subscription IDs in your database
  4. Use metadata: Pass your user IDs in metadata for easy lookup
  5. Handle all subscription states: active, past_due, canceled, etc.

Common Mistakes

  • Not handling invoice.payment_failed (users don't know payment failed)
  • Trusting client-side tier selection (always verify server-side)
  • Not setting up customer portal (users can't self-manage)
  • Forgetting to sync subscription status on webhook
  • Not testing with Stripe CLI locally

Security Checklist

  • [ ] Webhook signature verification
  • [ ] HTTPS only for webhook endpoints
  • [ ] Stripe keys in environment variables
  • [ ] Customer portal configured
  • [ ] Test mode vs live mode separation
  • [ ] PCI compliance (use Stripe Elements/Checkout)

More from this repository10

🎯
feature-flags🎯Skill

Enables controlled feature rollouts, A/B testing, and selective feature access through configurable flags for gradual deployment and user targeting.

🎯
design-tokens🎯Skill

Generates a comprehensive, type-safe design token system with WCAG AA color compliance and multi-framework support for consistent visual design.

🎯
file-uploads🎯Skill

Securely validates, scans, and processes file uploads with multi-stage checks, malware detection, and race condition prevention.

🎯
ai-coaching🎯Skill

Guides users through articulating creative intent by extracting structured parameters and detecting conversation readiness.

🎯
environment-config🎯Skill

Validates and centralizes environment variables with type safety, fail-fast startup checks, and multi-environment support.

🎯
community-feed🎯Skill

Generates efficient social feed with cursor pagination, trending algorithms, and engagement tracking for infinite scroll experiences.

🎯
cloud-storage🎯Skill

Enables secure, multi-tenant cloud file storage with signed URLs, direct uploads, and visibility control for user-uploaded assets.

🎯
email-service🎯Skill

Simplifies email sending, templating, and tracking with robust SMTP integration and support for multiple email providers and transactional workflows.

🎯
error-sanitization🎯Skill

Sanitizes error messages by logging full details server-side while exposing only generic, safe messages to prevent sensitive information leakage.

🎯
batch-processing🎯Skill

Optimizes database operations by collecting and batching independent records, improving throughput by 30-40% with built-in fallback processing.