关于 stripe-payments
stripe-payments 是一项为 Web 应用接入 Stripe 支付的开发技能,面向实现结账与账单功能的开发者。它提供一次性付款、订阅、Webhook 和客户门户等常见模式的示例代码,并说明 Cloudflare Workers 的注意事项。
主要功能
- 01Checkout Sessions 一次性付款与订阅
- 02Payment Element 与 Payment Intents
- 03Webhook 事件处理与签名验证
- 04客户门户和账单管理
- 05Stripe CLI 本地 Webhook 测试
适用场景
- 01为 Web 应用添加一次性付款
- 02实现订阅及状态检查
- 03处理支付成功或取消事件
- 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