重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
stripe-payments by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill stripe-payments为 Web 应用添加 Stripe 支付功能。涵盖常见模式——一次性支付、订阅、Webhook、客户门户——并提供可运行的代码。无需 MCP 服务器。
| 你想... | 使用 | 复杂度 |
|---|---|---|
| 接受一次性支付 | 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
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
# 从以下网址获取密钥:https://dashboard.stripe.com/apikeys
# 测试密钥以 sk_test_ 和 pk_test_ 开头
# 生产密钥以 sk_live_ 和 pk_live_ 开头
# 对于 Cloudflare Workers —— 存储为 secrets:
npx wrangler secret put STRIPE_SECRET_KEY
npx wrangler secret put STRIPE_WEBHOOK_SECRET
# 对于本地开发 —— .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!);
接受付款的最快方式。Stripe 托管整个结账页面。
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 });
});
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;
}
# 通过 Stripe CLI 创建(推荐用于设置)
stripe products create --name="Pro Plan" --description="Full access"
stripe prices create --product=prod_XXX --unit-amount=2900 --currency=aud --recurring[interval]=month
# 或通过 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`,
// 如果已知,链接到现有客户:
customer: customerId, // 或 customer_email: 'user@example.com'
});
async function hasActiveSubscription(customerId: string): Promise<boolean> {
const subs = await stripe.subscriptions.list({
customer: customerId,
status: 'active',
limit: 1,
});
return subs.data.length > 0;
}
当事件发生时(支付成功、订阅取消等),Stripe 会向你的服务器发送事件。你必须验证 Webhook 签名。
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 {
// 在 Workers 中使用 constructEventAsync(没有 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;
// 履行订单——更新数据库、发送邮件、授予访问权限
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 });
});
# 使用 Stripe CLI 进行本地测试:
stripe listen --forward-to http://localhost:8787/api/webhooks/stripe
# 生产环境——通过 Dashboard 注册:
# https://dashboard.stripe.com/webhooks
# URL: https://yourapp.com/api/webhooks/stripe
# 事件:checkout.session.completed, customer.subscription.updated,
# customer.subscription.deleted, invoice.payment_failed
constructEvent(同步)使用 Node.js 的 crypto,这在 Workers 中不存在。请改用 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 产品读取数据的定价页面:
// 服务器:获取产品和价格
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。
# 安装
brew install stripe/stripe-cli/stripe
# 登录
stripe login
# 本地监听 Webhooks
stripe listen --forward-to http://localhost:8787/api/webhooks/stripe
# 触发测试事件
stripe trigger checkout.session.completed
stripe trigger customer.subscription.created
stripe trigger invoice.payment_failed
// 在首次结账时,创建或查找客户:
const session = await stripe.checkout.sessions.create({
customer_email: user.email, // 如果不存在则创建新客户
// 或者
customer: user.stripeCustomerId, // 使用现有客户
metadata: { userId: user.id }, // 关联回你的用户
// ...
});
// 在 Webhook 中,保存客户 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,
},
// ...
});
// 创建价格时设置货币
const price = await stripe.prices.create({
product: 'prod_XXX',
unit_amount: 2900, // 以分为单位的 $29.00
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 重试 | Stripe 会重试失败的 Webhook,最长持续 3 天。请快速返回 200。 |
| 结账重定向时的 CORS | Checkout URL 在 stripe.com 上——使用 window.location.href,而不是 fetch |
每周安装量
65
代码仓库
GitHub 星标
643
首次出现
6 天前
安全审计
安装于
opencode64
codex63
gemini-cli63
warp63
kimi-cli63
github-copilot63
Add Stripe payments to a web app. Covers the common patterns — one-time payments, subscriptions, webhooks, customer portal — with working code. No MCP server needed.
| You want to... | Use | Complexity |
|---|---|---|
| Accept a one-time payment | Checkout Sessions | Low — Stripe hosts the payment page |
| Embed a payment form in your UI | Payment Element + Payment Intents | Medium — you build the form, Stripe handles the card |
| Recurring billing / subscriptions | Checkout Sessions (subscription mode) | Low-Medium |
| Save a card for later | Setup Intents | Low |
| Marketplace / platform payments | Stripe Connect | High |
| Let customers manage billing | Customer Portal | Low — Stripe hosts it |
Default recommendation : Start with Checkout Sessions. It's the fastest path to accepting money. You can always add embedded forms later.
npm install stripe @stripe/stripe-js
# 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!);
The fastest way to accept payment. Stripe hosts the entire checkout page.
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 });
});
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
Hardcode price IDs in your code (they don't change):
const PRICES = {
pro_monthly: 'price_1234567890',
pro_yearly: 'price_0987654321',
} as const;
Same as one-time but with 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: 'user@example.com'
});
async function hasActiveSubscription(customerId: string): Promise<boolean> {
const subs = await stripe.subscriptions.list({
customer: customerId,
status: 'active',
limit: 1,
});
return subs.data.length > 0;
}
Stripe sends events to your server when things happen (payment succeeded, subscription cancelled, etc.). You must verify the webhook signature.
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 });
});
# 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
constructEvent (synchronous) uses Node.js crypto which doesn't exist in Workers. Use constructEventAsync instead — it uses the Web Crypto API.
Let customers manage their own subscriptions (upgrade, downgrade, cancel, update payment method):
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 });
});
Configure the portal in Dashboard: https://dashboard.stripe.com/settings/billing/portal
Generate a pricing page that reads from Stripe products:
// 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,
})));
});
Or hardcode if you only have 2-3 plans — simpler and no API call on every page load.
# 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
// 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' },
});
| Gotcha | Fix |
|---|---|
constructEvent fails on Workers | Use constructEventAsync (Web Crypto API) |
| Webhook fires but handler not called | Check the endpoint URL matches exactly (trailing slash matters) |
| Test mode payments not appearing | Make sure you're using sk_test_ key, not sk_live_ |
| Price amounts are in cents | 2900 = $29.00. Always divide by 100 for display |
| Customer email doesn't match user | Use customer (existing ID) not for returning users |
Weekly Installs
65
Repository
GitHub Stars
643
First Seen
6 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
opencode64
codex63
gemini-cli63
warp63
kimi-cli63
github-copilot63
Lark Skill Maker 教程:基于飞书CLI创建AI技能,自动化工作流与API调用指南
41,900 周安装
customer_email| Subscription status stale | Don't cache — check via API or trust webhook events |
| Webhook retries | Stripe retries failed webhooks for up to 3 days. Return 200 quickly. |
| CORS on checkout redirect | Checkout URL is on stripe.com — use window.location.href, not fetch |