Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问许可证需确认审计提醒

stripe-integrationStripe 集成

Agent Skill

stripe-integration 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

749

周安装

30

GitHub Stars

777

下载量

242
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:stripe-integration(Stripe 集成)
来源仓库:https://github.com/dadbodgeoff/drift
仓库路径:skills/stripe-integration
安装命令:
npx skills add https://github.com/dadbodgeoff/drift --skill stripe-integration
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dadbodgeoff/drift --skill stripe-integration

简介

stripe-integration 用于 SaaS 订阅与用量计费,适合在 Codex、Claude、Cursor、Gemini CLI 中需要处理支付、webhook 与客户自助时使用。

  • 它封装客户、订阅与发票对象,支持 webhooks 安全验证与数据库同步,提供完整账单流程。
  • 使用时需妥善处理 webhook 重放与幂等性,避免重复扣款;建议沙箱环境充分测试。
  • 安装前请配置 Stripe 密钥与环境变量,注意是否会发起真实交易,确保测试用例隔离生产。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

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

# .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

// 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

// 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<string> {
  // 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

// 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

// api/create-portal.ts
export async function createPortalSession(userId: string): Promise<string> {
  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

# 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

// 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 (
    <button onClick={handleCheckout} disabled={loading}>
      {loading ? 'Loading...' : children}
    </button>
  );
}

Database Schema

-- 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

# 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)

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

36.56%
按下载量换算88

Claude

28.17%
按下载量换算68

Cursor

19.18%
按下载量换算46

Gemini CLI

8.55%
按下载量换算21

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills