mcpField
S
stripe-payments

stripe-payments

github.com/jezweb开发1.1k1.1k 次安装3 周前更新
代码仓库

关于 stripe-payments

stripe-payments 是一项为 Web 应用接入 Stripe 支付的开发技能,面向实现结账与账单功能的开发者。它提供一次性付款、订阅、Webhook 和客户门户等常见模式的示例代码,并说明 Cloudflare Workers 的注意事项。

主要功能

  1. 01Checkout Sessions 一次性付款与订阅
  2. 02Payment Element 与 Payment Intents
  3. 03Webhook 事件处理与签名验证
  4. 04客户门户和账单管理
  5. 05Stripe CLI 本地 Webhook 测试

适用场景

  1. 01为 Web 应用添加一次性付款
  2. 02实现订阅及状态检查
  3. 03处理支付成功或取消事件
  4. 04让客户管理订阅和付款方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jezweb/claude-skills --skill stripe-payments

安装后在 Claude Code 中输入/stripe-payments确认已加载。

详细介绍

Stripe Payments

为 Web 应用添加 Stripe 支付功能。涵盖常见模式——一次性付款、订阅、Webhook、客户门户,并提供可运行的代码。无需 MCP 服务。

我需要哪种 Stripe API?

你想要……使用复杂度
接受一次性付款Checkout Sessions低——支付页面由 Stripe 托管
在 UI 中嵌入支付表单Payment Element + Payment Intents中——由你构建表单,Stripe 处理银行卡
周期性账单/订阅Checkout Sessions(订阅模式)低至中
保存银行卡以便日后使用Setup Intents低
市场/平台支付Stripe Connect高
让客户管理账单Customer Portal低——由 Stripe 托管

默认建议:从 Checkout Sessions 开始。这是最快的收款方式。你始终可以稍后再添加嵌入式表单。

设置

安装
npm install stripe @stripe/stripe-js
API 密钥
# Get keys from: https://dashboard.stripe.com/apikeys
# Test keys start with sk_test_ and pk_test_
# Live keys start with sk_live_ and pk_live_

# For Cloudflare Workers — store as secrets:
npx wrangler secret put STRIPE_SECRET_KEY
npx wrangler secret put STRIPE_WEBHOOK_SECRET

# For local dev — .dev.vars:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
服务端客户端
import Stripe from 'stripe';

// Cloudflare Workers
const stripe = new Stripe(c.env.STRIPE_SECRET_KEY);

// Node.js
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

一次性付款(Checkout Sessions)

最快的收款方式。整个结账页面由 Stripe 托管。

创建 Checkout Session(服务端)
app.post('/api/checkout', async (c) => {
  const { priceId, successUrl, cancelUrl } = await c.req.json();

  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: successUrl || `${new URL(c.req.url).origin}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: cancelUrl || `${new URL(c.req.url).origin}/pricing`,
  });

  return c.json({ url: session.url });
});
重定向到 Checkout(客户端)
async function handleCheckout(priceId: string) {
  const res = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ priceId }),
  });
  const { url } = await res.json();
  window.location.href = url;
}
创建产品和价格
# Via Stripe CLI (recommended for setup)
stripe products create --name="Pro Plan" --description="Full access"
stripe prices create --product=prod_XXX --unit-amount=2900 --currency=aud --recurring[interval]=month

# Or via Dashboard: https://dashboard.stripe.com/products

在代码中硬编码价格 ID(它们不会更改):

const PRICES = {
  pro_monthly: 'price_1234567890',
  pro_yearly: 'price_0987654321',
} as const;

订阅

与一次性付款相同,但使用 mode: 'subscription':

const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],
  success_url: `${origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${origin}/pricing`,
  // Link to existing customer if known:
  customer: customerId, // or customer_email: '[email protected]'
});
检查订阅状态
async function hasActiveSubscription(customerId: string): Promise<boolean> {
  const subs = await stripe.subscriptions.list({
    customer: customerId,
    status: 'active',
    limit: 1,
  });
  return subs.data.length > 0;
}

Webhook

发生事件时(付款成功、订阅取消等),Stripe 会向你的服务端发送事件。必须验证 Webhook 签名。

Webhook 处理程序(Cloudflare Workers / Hono)
app.post('/api/webhooks/stripe', async (c) => {
  const body = await c.req.text();
  const sig = c.req.header('stripe-signature')!;

  let event: Stripe.Event;
  try {
    // Use constructEventAsync for Workers (no Node crypto)
    event = await stripe.webhooks.constructEventAsync(
      body,
      sig,
      c.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return c.json({ error: 'Invalid signature' }, 400);
  }

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      // Fulfill the order — update database, send email, grant access
      await handleCheckoutComplete(session);
      break;
    }
    case 'customer.subscription.updated': {
      const sub = event.data.object as Stripe.Subscription;
      await handleSubscriptionChange(sub);
      break;
    }
    case 'customer.subscription.deleted': {
      const sub = event.data.object as Stripe.Subscription;
      await handleSubscriptionCancelled(sub);
      break;
    }
    case 'invoice.payment_failed': {
      const invoice = event.data.object as Stripe.Invoice;
      await handlePaymentFailed(invoice);
      break;
    }
  }

  return c.json({ received: true });
});
注册 Webhook
# Local testing with Stripe CLI:
stripe listen --forward-to http://localhost:8787/api/webhooks/stripe

# Production — register via Dashboard:
# https://dashboard.stripe.com/webhooks
# URL: https://yourapp.com/api/webhooks/stripe
# Events: checkout.session.completed, customer.subscription.updated,
#          customer.subscription.deleted, invoice.payment_failed
Cloudflare Workers 注意事项

同步的 constructEvent 使用 Workers 中不存在的 Node.js crypto。请改用 constructEventAsync——它使用 Web Crypto API。

客户门户

让客户自行管理订阅(升级、降级、取消、更新付款方式):

app.post('/api/billing/portal', async (c) => {
  const { customerId } = await c.req.json();

  const session = await stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: `${new URL(c.req.url).origin}/dashboard`,
  });

  return c.json({ url: session.url });
});

在 Dashboard 中配置门户:https://dashboard.stripe.com/settings/billing/portal

定价页面模式

生成一个从 Stripe 产品读取数据的定价页面:

// Server: fetch products and prices
app.get('/api/pricing', async (c) => {
  const prices = await stripe.prices.list({
    active: true,
    expand: ['data.product'],
    type: 'recurring',
  });

  return c.json(prices.data.map(price => ({
    id: price.id,
    name: (price.product as Stripe.Product).name,
    description: (price.product as Stripe.Product).description,
    amount: price.unit_amount,
    currency: price.currency,
    interval: price.recurring?.interval,
  })));
});

如果只有 2-3 个套餐,也可以硬编码——这样更简单,而且每次加载页面时都不需要调用 API。

Stripe CLI(本地开发)

# Install
brew install stripe/stripe-cli/stripe

# Login
stripe login

# Listen for webhooks locally
stripe listen --forward-to http://localhost:8787/api/webhooks/stripe

# Trigger test events
stripe trigger checkout.session.completed
stripe trigger customer.subscription.created
stripe trigger invoice.payment_failed

常见模式

将 Stripe 客户关联到你的用户
// On first checkout, create or find customer:
const session = await stripe.checkout.sessions.create({
  customer_email: user.email,  // Creates new customer if none exists
  // OR
  customer: user.stripeCustomerId,  // Use existing
  metadata: { userId: user.id },  // Link back to your user
  // ...
});

// In webhook, save the customer ID:
case 'checkout.session.completed': {
  const session = event.data.object;
  await db.update(users)
    .set({ stripeCustomerId: session.customer as string })
    .where(eq(users.id, session.metadata.userId));
}
免费试用
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],
  subscription_data: {
    trial_period_days: 14,
  },
  // ...
});
澳元
// Set currency when creating prices
const price = await stripe.prices.create({
  product: 'prod_XXX',
  unit_amount: 2900,  // $29.00 in cents
  currency: 'aud',
  recurring: { interval: 'month' },
});

注意事项

注意事项修复方法
constructEvent 在 Workers 中失败使用 constructEventAsync(Web Crypto API)
Webhook 已触发,但处理程序未调用检查端点 URL 是否完全一致(尾部斜杠很重要)
测试模式的付款未显示确认使用的是 sk_test_ 密钥,而不是 sk_live_
价格金额以分为单位2900 = $29.00。显示时务必除以 100
客户邮箱与用户不匹配对回头客使用 customer(现有 ID),而不是 customer_email
订阅状态过时不要缓存——通过 API 检查,或信任 Webhook 事件
Webhook 重试对于失败的 Webhook,Stripe 最多会重试 3 天。请尽快返回 200。
结账重定向时的 CORS 问题Checkout URL 位于 stripe.com——使用 window.location.href,不要使用 fetch

评论

支持 Markdown