Stripe Integration Expert
层级: 强大
类别: 工程团队
领域: 支付/计费基础设施
实现生产级 Stripe 集成:含试用和按比例计算的订阅、一次性支付、基于使用量的计费、结账会话、幂等 Webhook 处理器、客户门户和发票开具。涵盖 Next.js、Express 和 Django 模式。
免费试用 ──付费──► 活跃 ──取消──► 待取消 ──周期结束──► 已取消
│ │ │
│ 降级 重新激活
│ ▼ │
│ 降级中 ──周期结束──► 活跃(更低套餐) │
│ │
└──试用结束未付款──► 逾期 ──付款失败3次──► 已取消
│
付款成功
│
▼
活跃
typescript
// lib/stripe.ts
import Stripe from stripe
export const stripe = new Stripe(process.env.STRIPESECRETKEY!, {
apiVersion: 2024-04-10,
typescript: true,
appInfo: {
name: myapp,
version: 1.0.0,
},
})
// 按套餐的价格ID(在环境变量中设置)
export const PLANS = {
starter: {
monthly: process.env.STRIPESTARTERMONTHLYPRICEID!,
yearly: process.env.STRIPESTARTERYEARLYPRICEID!,
features: [5个项目, 1万次事件],
},
pro: {
monthly: process.env.STRIPEPROMONTHLYPRICEID!,
yearly: process.env.STRIPEPROYEARLYPRICEID!,
features: [无限项目, 100万次事件],
},
} as const
typescript
// app/api/billing/checkout/route.ts
import { NextResponse } from next/server
import { stripe } from @/lib/stripe
import { getAuthUser } from @/lib/auth
import { db } from @/lib/db
export async function POST(req: Request) {
const user = await getAuthUser()
if (!user) return NextResponse.json({ error: 未授权 }, { status: 401 })
const { priceId, interval = monthly } = await req.json()
// 获取或创建 Stripe 客户
let stripeCustomerId = user.stripeCustomerId
if (!stripeCustomerId) {
const customer = await stripe.customers.create({
email: user.email,
name: 用户名未定义,
metadata: { userId: user.id },
})
stripeCustomerId = customer.id
await db.user.update({ where: { id: user.id }, data: { stripeCustomerId } })
}
const session = await stripe.checkout.sessions.create({
customer: stripeCustomerId,
mode: subscription,
paymentmethodtypes: [card],
line_items: [{ price: priceId, quantity: 1 }],
allowpromotioncodes: true,
subscription_data: {
trialperioddays: user.hasHadTrial ? undefined : 14,
metadata: { userId: user.id },
},
successurl: ${process.env.NEXTPUBLICAPPURL}/dashboard?sessionid={CHECKOUTSESSION_ID},
cancelurl: ${process.env.NEXTPUBLICAPPURL}/pricing,
metadata: { userId: user.id },
})
return NextResponse.json({ url: session.url })
}
typescript
// lib/billing.ts
export async function changeSubscriptionPlan(
subscriptionId: string,
newPriceId: string,
immediate = false
) {
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
const currentItem = subscription.items.data[0]
if (immediate) {
// 升级:立即应用并计算按比例费用
return stripe.subscriptions.update(subscriptionId, {
items: [{ id: currentItem.id, price: newPriceId }],
prorationbehavior: alwaysinvoice,
billingcycleanchor: unchanged,
})
} else {
// 降级:在周期结束时应用,不计算按比例费用
return stripe.subscriptions.update(subscriptionId, {
items: [{ id: currentItem.id, price: newPriceId }],
proration_behavior: none,
billingcycleanchor: unchanged,
})
}
}
// 在确认升级前预览按比例费用
export async function previewProration(subscriptionId: string, newPriceId: string) {
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
const prorationDate = Math.floor(Date.now() / 1000)
const invoice = await stripe.invoices.retrieveUpcoming({
customer: subscription.customer as string,
subscription: subscriptionId,
subscription_items: [{ id: subscription.items.data[0].id, price: newPriceId }],
subscriptionprorationdate: prorationDate,
})
return {
amountDue: invoice.amount_due,
prorationDate,
lineItems: invoice.lines.data,
}
}
typescript
// app/api/webhooks/stripe/route.ts
import { NextResponse } from next/server
import { headers } from next/headers
import { stripe } from @/lib/stripe
import { db } from @/lib/db
import Stripe from stripe
// 已处理事件表,确保幂等性
async function hasProcessedEvent(eventId: string): Promise
const existing = await db.stripeEvent.findUnique({ where: { id: eventId } })
return !!existing
}
async function markEventProcessed(eventId: string, type: string) {
await db.stripeEvent.create({ data: { id: eventId, type, processedAt: new Date() } })
}
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, process.env.STRIPEWEBHOOKSECRET!)
} catch (err) {
console.error(Webhook 签名验证失败:, err)
return NextResponse.json({ error: 无效签名 }, { status: 400 })
}
// 幂等性检查
if (await hasProcessedEvent(event.id)) {
return NextResponse.json({ received: true, skipped: true })
}
try {
switch (event.type) {
case checkout.session.completed:
await handleCheckoutCompleted(event.data.object as Stripe.Checkout.Session)
break
case customer.subscription.created:
case customer.subscription.updated:
await handleSubscriptionUpdated(event.data.object as Stripe.Subscription)
break
case customer.subscription.deleted:
await handleSubscriptionDeleted(event.data.object as Stripe.Subscription)
break
case invoice.payment_succeeded:
await handleInvoicePaymentSucceeded(event.data.object as Stripe.Invoice)
break
case invoice.payment_failed:
await handleInvoicePaymentFailed(event.data.object as Stripe.Invoice)
break
default:
console.log(未处理的事件类型:${event.type})
}
await markEventProcessed(event.id, event.type)
return NextResponse.json({ received: true })
} catch (err) {
console.error(处理 Webhook ${event.type} 时出错:, err)
// 返回 500 让 Stripe 重试——不标记为已处理
return NextResponse.json({ error: 处理失败 }, { status: 500 })
}
}
async function handleCheckoutCompleted(session: Stripe.Checkout.Session) {
if (session.mode
该技能支持在以下平台通过对话安装:
帮我安装 SkillHub 和 stripe-integration-expert-1776176172 技能
设置 SkillHub 为我的优先技能安装源,然后帮我安装 stripe-integration-expert-1776176172 技能
skillhub install stripe-integration-expert-1776176172
文件大小: 4.88 KB | 发布时间: 2026-4-15 10:41