Stripe Checkout 接入:从创建到上线的完整指南
如果你的 AI 产品需要出海收款,支付环节的第一站大概率是 Stripe。而在 Stripe 的所有接入方式里,Checkout 是上手最快、代码量最少的一种。它把支付表单、卡品牌验证、3DS 认证、多币种结算这些复杂逻辑全部封装在一个托管页面中,你只需要创建一个 Session,然后引导用户跳过去就行。
本章把 Stripe Checkout 的接入过程拆解为可执行的步骤,附带完整代码和测试方案。
什么是 Stripe Checkout
Stripe Checkout 是 Stripe 提供的托管支付页面(Hosted Payment Page)。它不是嵌入你网站的一个组件,而是一个由 Stripe 托管的独立页面。用户在页面上输入卡号、完成支付,然后被重定向回你的站点。
它的核心概念是 Checkout Session——一个服务端对象,定义了这笔交易的完整信息:卖什么、多少钱、用什么货币、支付成功后跳转到哪里。你通过 API 创建这个 Session,拿到一个 URL,然后把用户带过去。
Checkout 支持三种模式:
| 模式 | 用途 | 典型场景 |
|---|---|---|
payment | 一次性付款 | 购买 AI 积分包、单次报告 |
subscription | 周期性订阅 | 月度/年度 SaaS 会员 |
setup | 保存支付方式 | 先绑定卡,后续再扣款 |
对于独立开发者或小团队来说,Checkout 的价值在于:你不需要处理任何 PCI 合规问题,不需要自己构建支付表单,也不需要关心各国卡品牌的兼容性。这些全部由 Stripe 托管页面处理。
接入步骤概览
一个完整的 Checkout 接入分为五个阶段:
下面逐步展开。
第一步:注册 Stripe 账号并获取 API Keys
登录 Stripe Dashboard,在 Developers → API keys 中获取两个密钥:
- Publishable key(
pk_test_...):可暴露到前端,用于初始化 Stripe.js - Secret key(
sk_test_...):只能存在服务端,用于调用 Stripe API
测试环境的 key 以 _test_ 开头,正式环境以 _live_ 开头。在开发阶段始终使用测试 key,不会产生真实扣款。
第二步:创建 Product 和 Price
在 Stripe Dashboard 的 Product catalog 中创建产品,或通过 API 创建:
// 创建一次性付费产品
const product = await stripe.products.create({
name: 'AI Pro 月度会员',
description: '解锁全部 AI 功能,每月 1000 次调用额度',
})
// 为产品创建价格
const price = await stripe.prices.create({
product: product.id,
unit_amount: 2999, // 单位:分,即 $29.99
currency: 'usd',
recurring: { interval: 'month' }, // 月付订阅
})创建完成后你会拿到一个 price_xxxx 格式的 Price ID,后续创建 Checkout Session 时会用到。
第三步:后端创建 Checkout Session
这是整个接入的核心步骤。你需要在后端暴露一个 API 端点,接收前端请求后创建 Session 并返回跳转 URL。
Next.js App Router 示例:
// app/api/checkout/route.ts
import Stripe from 'stripe'
import { NextRequest, NextResponse } from 'next/server'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia',
})
export async function POST(req: NextRequest) {
const { priceId, mode = 'subscription' } = await req.json()
const session = await stripe.checkout.sessions.create({
mode: mode as Stripe.Checkout.SessionCreateParams.Mode,
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity: 1,
},
],
// 支付成功后跳转,{CHECKOUT_SESSION_ID} 会被 Stripe 自动替换
success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
// 用户取消支付后跳转
cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/pricing`,
// 自动收集账单地址
billing_address_collection: 'auto',
// 始终创建 Customer 对象,方便后续管理
customer_creation: 'always',
})
return NextResponse.redirect(session.url!, 303)
}Python Flask 示例:
# server.py
import stripe
from flask import Flask, redirect, request
stripe.api_key = 'sk_test_xxxx'
app = Flask(__name__)
@app.route('/create-checkout-session', methods=['POST'])
def create_checkout_session():
session = stripe.checkout.Session.create(
mode='subscription',
payment_method_types=['card'],
line_items=[{
'price': request.json.get('price_id'),
'quantity': 1,
}],
success_url='https://yourdomain.com/payment/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url='https://yourdomain.com/pricing',
billing_address_collection='auto',
customer_creation='always',
)
return redirect(session.url, code=303)第四步:前端跳转
前端只需要一个简单的表单,将用户引导到后端创建的 Session URL:
// components/CheckoutButton.tsx
export function CheckoutButton({ priceId }: { priceId: string }) {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
// 提交到后端 API,后端会 303 重定向到 Stripe
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
})
// fetch 不会自动跟随 303,需要手动跳转
window.location.href = response.url
}
return (
<form onSubmit={handleSubmit}>
<button type="submit" className="btn-primary">
订阅 Pro 会员 — $29.99/月
</button>
</form>
)
}也可以使用 Stripe.js 在客户端直接跳转(不经过后端重定向):
import { loadStripe } from '@stripe/stripe-js'
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!)
// 获取 session ID 后直接跳转
const { error } = await stripe.redirectToCheckout({ sessionId })第五步:处理支付结果
用户完成支付后会被重定向到 success_url。但不要仅依赖前端跳转来确认支付——用户可能关闭页面,也可能支付实际上失败了。正确做法是通过 Webhook 在后端接收支付事件。
// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe'
import { NextRequest } from 'next/server'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
export async function POST(req: NextRequest) {
const body = await req.text()
const signature = req.headers.get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
} catch (err) {
return new Response('Webhook signature verification failed', { status: 400 })
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
// 支付成功,执行履约逻辑
// 例如:更新用户权限、发送确认邮件、记录订单
await fulfillOrder(session)
break
}
case 'customer.subscription.updated': {
// 订阅状态变更(续费成功、降级、取消等)
await handleSubscriptionChange(event.data.object)
break
}
case 'invoice.payment_failed': {
// 续费失败,通知用户更新支付方式
await handlePaymentFailure(event.data.object)
break
}
}
return new Response('ok', { status: 200 })
}在开发环境,可以使用 Stripe CLI 将 Webhook 事件转发到本地:
stripe listen --forward-to localhost:3000/api/webhooks/stripe运行后会输出一个 whsec_... 格式的 Webhook Secret,设置到环境变量即可。
自定义选项
Checkout 虽然是托管页面,但提供了不少自定义空间。
品牌外观
在 Stripe Dashboard → Settings → Branding 中可以配置:
- Logo:上传品牌 Logo,会显示在支付页面顶部
- Icon:小尺寸图标,用于移动端展示
- 品牌色:设置主色调,影响按钮和链接颜色
- 字体:支持自定义字体文件
- 按钮样式:圆角大小、背景色等
这些设置对所有 Checkout 页面和 Payment Links 生效。
语言与地区
Checkout 会根据浏览器语言自动选择页面语言,支持 35+ 种语言。你也可以通过 locale 参数强制指定:
const session = await stripe.checkout.sessions.create({
// ...其他参数
locale: 'zh-CN', // 强制中文
// 限制允许的国家
shipping_address_collection: {
allowed_countries: ['US', 'CA', 'GB', 'DE', 'JP', 'SG'],
},
})自定义字段
你可以要求用户在 Checkout 页面额外填写信息,比如公司名称、优惠码、备注:
const session = await stripe.checkout.sessions.create({
// ...其他参数
custom_fields: [
{
key: 'company_name',
label: { type: 'custom', custom: '公司名称(选填)' },
type: 'text',
optional: true,
},
{
key: 'referral_code',
label: { type: 'custom', custom: '推荐码' },
type: 'text',
optional: true,
},
],
})支付方式
默认情况下,Checkout 会自动展示当前地区可用的支付方式(信用卡、Apple Pay、Google Pay 等)。你也可以手动指定:
const session = await stripe.checkout.sessions.create({
// ...其他参数
payment_method_types: ['card', 'alipay', 'wechat_pay'],
payment_method_options: {
wechat_pay: {
client: 'web', // 在网页端使用微信支付
},
},
})自定义域名
如果你希望支付页面的 URL 使用你自己的域名(比如 checkout.yourdomain.com),可以在 Stripe Dashboard 中配置自定义域名,提升用户信任度。
测试模式
Stripe 提供了完整的测试环境,让你在不上线的情况下验证整个支付流程。
测试 Key
测试环境和正式环境使用不同的 API Key:
| 环境 | Publishable Key | Secret Key |
|---|---|---|
| 测试 | pk_test_... | sk_test_... |
| 正式 | pk_live_... | sk_live_... |
使用测试 Key 时,所有交易都是模拟的,不会产生真实扣款。
测试卡号
Stripe 提供了一组专用的测试卡号来模拟不同场景:
| 场景 | 卡号 | 说明 |
|---|---|---|
| 支付成功 | 4242 4242 4242 4242 | 最常用的测试卡 |
| 需要 3DS 认证 | 4000 0025 0000 3155 | 模拟 SCA 强认证流程 |
| 余额不足 | 4000 0000 0000 9995 | 模拟 declined - insufficient_funds |
| 卡片过期 | 4000 0000 0000 0069 | 模拟 expired_card |
| CVC 错误 | 4000 0000 0000 0127 | 模拟 incorrect_cvc |
| 通用拒绝 | 4000 0000 0000 0002 | 模拟 generic_decline |
| 盗用卡 | 4000 0000 0000 9979 | 模拟 stolen_card |
| Radar 风控拦截 | 4100 0000 0000 0019 | 模拟被 Stripe Radar 标记为高风险 |
测试时,CVC 填任意 3 位数字,有效期填任意未来日期,邮编填任意有效值即可。
模拟不同国家的用户
在 Checkout Session 中,使用特殊格式的邮箱可以模拟不同国家的支付方式:
[email protected] → 模拟日本用户
[email protected] → 模拟德国用户
[email protected] → 模拟美国用户
模拟 Webhook 事件
除了 Stripe CLI 转发,你也可以在 Dashboard 的 Webhook 设置中手动触发测试事件,验证后端处理逻辑是否正确。
方案对比
Checkout vs Elements vs Payment Links
| 维度 | Checkout | Elements | Payment Links |
|---|---|---|---|
| 类型 | Stripe 托管页面 | 嵌入式 UI 组件 | 无代码托管页面 |
| 代码量 | 中等(后端创建 Session) | 较多(前端集成 + 后端逻辑) | 零代码(Dashboard 配置) |
| 自定义程度 | 中等(品牌、字段、语言) | 高(像素级控制 UI) | 低(基础品牌设置) |
| PCI 合规 | Stripe 全权处理 | 通过 Stripe.js 处理 | Stripe 全权处理 |
| 内置功能 | 税费、订阅、折扣、运费、地址收集 | 需自行实现部分功能 | 基础支付和订阅 |
| 适用场景 | 大多数业务 | 需要高度定制支付体验 | 快速验证、简单收款 |
| 维护成本 | 低 | 高 | 极低 |
Checkout 各接入模式对比
| 维度 | 完整托管页面 | 嵌入式 Checkout | Checkout + Elements |
|---|---|---|---|
| 用户体验 | 跳转到 Stripe 页面 | 留在你的网站内 | 留在你的网站内 |
| UI 控制权 | Stripe 控制 | Stripe 控制(嵌入 iframe) | 你控制布局,Stripe 控制支付组件 |
| 实现复杂度 | 最低 | 低 | 中等 |
| 适合场景 | 快速上线 | 希望无缝体验但不想自建 | 需要自定义布局 |
Checkout Session 关键参数速查
| 参数 | 类型 | 说明 |
|---|---|---|
mode | 'payment' / 'subscription' / 'setup' | 交易模式 |
line_items | Array<{price, quantity}> | 商品列表 |
success_url | string | 支付成功跳转 URL |
cancel_url | string | 取消支付跳转 URL |
customer_email | string | 预填用户邮箱 |
customer | string (Customer ID) | 关联已有 Customer |
billing_address_collection | 'auto' / 'required' | 账单地址收集 |
shipping_address_collection | {allowed_countries: string[]} | 收货地址收集 |
locale | string | 强制指定页面语言 |
custom_fields | Array | 自定义用户填写字段 |
automatic_tax | {enabled: boolean} | 启用 Stripe Tax 自动计税 |
metadata | Record<string, string> | 附加元数据,会在 Webhook 中返回 |
submit_type | 'pay' / 'donate' / 'book' / 'auto' | 提交按钮文案 |
customer_creation | 'always' / 'if_required' | 是否始终创建 Customer 对象 |
常用测试卡号速查
| 用途 | 卡号 | CVC | 有效期 |
|---|---|---|---|
| Visa 支付成功 | 4242 4242 4242 4242 | 任意 3 位 | 任意未来日期 |
| Mastercard 支付成功 | 5555 5555 5555 4444 | 任意 3 位 | 任意未来日期 |
| 3DS 认证(成功后通过) | 4000 0025 0000 3155 | 任意 3 位 | 任意未来日期 |
| 余额不足拒绝 | 4000 0000 0000 9995 | 任意 3 位 | 任意未来日期 |
| 盗用卡拒绝 | 4000 0000 0000 9979 | 任意 3 位 | 任意未来日期 |
| Radar 风控拦截 | 4100 0000 0000 0019 | 任意 3 位 | 任意未来日期 |
案例
案例一:AI 写作工具的积分包销售
某 AI 写作工具需要销售一次性积分包($9.99 / 100 次调用)。接入流程:
- 在 Stripe 创建产品「AI Writing Credits」,设置一次性价格 $9.99
- 后端创建一个
/api/checkout端点,接收用户选择的积分包类型 - 创建 Checkout Session 时使用
mode: 'payment',传入对应 Price ID - 在
metadata中附加userId和creditAmount,方便 Webhook 回调时识别 - Webhook 收到
checkout.session.completed事件后,根据 metadata 给对应用户增加积分
关键代码片段:
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: 'price_xxx', quantity: 1 }],
metadata: {
userId: 'usr_abc123',
creditAmount: '100',
packageType: 'starter',
},
success_url: `${BASE_URL}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${BASE_URL}/pricing`,
})这个方案从开始接入到测试通过,用时不到半天。
案例二:SaaS 产品的订阅管理
一个项目管理 SaaS 产品提供三档订阅($19 / $49 / $99 月付)。接入流程:
- 为每档创建独立的 Price(
recurring: { interval: 'month' }),同时创建年付价格(年付享 20% 折扣) - 在定价页面展示三档方案,用户点击后带上 Price ID 请求后端
- 创建 Checkout Session 时使用
mode: 'subscription' - 通过 Webhook 监听
customer.subscription.created、customer.subscription.updated、customer.subscription.deleted事件 - 根据订阅状态更新用户权限,在
subscription.status为active时提供完整功能
这个方案需要注意的点:
- 使用
customer_creation: 'always'确保每次订阅都关联一个 Customer 对象 - 在
success_url中传递session_id,成功页通过 API 查询 Session 获取订阅状态 - 订阅升级/降级需要创建新的 Checkout Session,Stripe 会自动处理按比例计费(proration)
Checkout 接入检查清单
在上线前逐项确认:
- API Key 已从测试环境切换到正式环境(
_test_→_live_) - Webhook 端点已配置并验证签名(使用
STRIPE_WEBHOOK_SECRET) -
success_url和cancel_url使用完整的绝对路径,不是相对路径 - 不要仅依赖前端跳转确认支付,后端必须通过 Webhook 验证
-
line_items中的 Price ID 与前端传入的 ID 一致,防止价格篡改 - 金额和商品信息由服务端控制,不从客户端接收价格参数
- 已使用测试卡号验证过成功、失败、3DS 认证等场景
- 已配置品牌外观(Logo、品牌色),支付页不会显得「裸奔」
-
billing_address_collection和shipping_address_collection按需设置 - 错误处理覆盖:网络超时、签名验证失败、Webhook 重试幂等性
- 已设置自定义域名或使用品牌 Logo,避免用户看到 Stripe 品牌时产生疑虑
- 订阅场景下已处理
invoice.payment_failed事件,通知用户更新支付方式 - 正式环境的 Dashboard 通知设置已配置(支付提醒、争议提醒等)
参考资料
- Stripe Checkout 官方文档 — Checkout 完整 API 参考
- Build a Stripe-hosted checkout page — 官方 Quick Start,含多语言代码示例
- How Checkout works — Checkout 工作原理详解
- Customize Checkout — 品牌、字段、域名等自定义选项
- Compare Checkout Sessions and Payment Intents APIs — Checkout Sessions vs Payment Intents 对比
- Test card numbers — 全部测试卡号和测试场景
- Stripe CLI 文档 — 本地开发环境 Webhook 调试
- stripe-samples/checkout-one-time-payments — 官方一次性支付示例代码