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 接入分为五个阶段:

流程图画布 · 115%
Mermaid 流程图加载中...

下面逐步展开。

第一步:注册 Stripe 账号并获取 API Keys

登录 Stripe Dashboard,在 Developers → API keys 中获取两个密钥:

  • Publishable keypk_test_...):可暴露到前端,用于初始化 Stripe.js
  • Secret keysk_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 KeySecret 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 设置中手动触发测试事件,验证后端处理逻辑是否正确。

方案对比

维度CheckoutElementsPayment Links
类型Stripe 托管页面嵌入式 UI 组件无代码托管页面
代码量中等(后端创建 Session)较多(前端集成 + 后端逻辑)零代码(Dashboard 配置)
自定义程度中等(品牌、字段、语言)高(像素级控制 UI)低(基础品牌设置)
PCI 合规Stripe 全权处理通过 Stripe.js 处理Stripe 全权处理
内置功能税费、订阅、折扣、运费、地址收集需自行实现部分功能基础支付和订阅
适用场景大多数业务需要高度定制支付体验快速验证、简单收款
维护成本极低

Checkout 各接入模式对比

维度完整托管页面嵌入式 CheckoutCheckout + Elements
用户体验跳转到 Stripe 页面留在你的网站内留在你的网站内
UI 控制权Stripe 控制Stripe 控制(嵌入 iframe)你控制布局,Stripe 控制支付组件
实现复杂度最低中等
适合场景快速上线希望无缝体验但不想自建需要自定义布局

Checkout Session 关键参数速查

参数类型说明
mode'payment' / 'subscription' / 'setup'交易模式
line_itemsArray&lt;&#123;price, quantity&#125;&gt;商品列表
success_urlstring支付成功跳转 URL
cancel_urlstring取消支付跳转 URL
customer_emailstring预填用户邮箱
customerstring (Customer ID)关联已有 Customer
billing_address_collection'auto' / 'required'账单地址收集
shipping_address_collection&#123;allowed_countries: string[]&#125;收货地址收集
localestring强制指定页面语言
custom_fieldsArray自定义用户填写字段
automatic_tax&#123;enabled: boolean&#125;启用 Stripe Tax 自动计税
metadataRecord&lt;string, string&gt;附加元数据,会在 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 次调用)。接入流程:

  1. 在 Stripe 创建产品「AI Writing Credits」,设置一次性价格 $9.99
  2. 后端创建一个 /api/checkout 端点,接收用户选择的积分包类型
  3. 创建 Checkout Session 时使用 mode: 'payment',传入对应 Price ID
  4. metadata 中附加 userIdcreditAmount,方便 Webhook 回调时识别
  5. 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 月付)。接入流程:

  1. 为每档创建独立的 Price(recurring: &#123; interval: 'month' &#125;),同时创建年付价格(年付享 20% 折扣)
  2. 在定价页面展示三档方案,用户点击后带上 Price ID 请求后端
  3. 创建 Checkout Session 时使用 mode: 'subscription'
  4. 通过 Webhook 监听 customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deleted 事件
  5. 根据订阅状态更新用户权限,在 subscription.statusactive 时提供完整功能

这个方案需要注意的点:

  • 使用 customer_creation: 'always' 确保每次订阅都关联一个 Customer 对象
  • success_url 中传递 session_id,成功页通过 API 查询 Session 获取订阅状态
  • 订阅升级/降级需要创建新的 Checkout Session,Stripe 会自动处理按比例计费(proration)

Checkout 接入检查清单

在上线前逐项确认:

  • API Key 已从测试环境切换到正式环境(_test__live_
  • Webhook 端点已配置并验证签名(使用 STRIPE_WEBHOOK_SECRET
  • success_urlcancel_url 使用完整的绝对路径,不是相对路径
  • 不要仅依赖前端跳转确认支付,后端必须通过 Webhook 验证
  • line_items 中的 Price ID 与前端传入的 ID 一致,防止价格篡改
  • 金额和商品信息由服务端控制,不从客户端接收价格参数
  • 已使用测试卡号验证过成功、失败、3DS 认证等场景
  • 已配置品牌外观(Logo、品牌色),支付页不会显得「裸奔」
  • billing_address_collectionshipping_address_collection 按需设置
  • 错误处理覆盖:网络超时、签名验证失败、Webhook 重试幂等性
  • 已设置自定义域名或使用品牌 Logo,避免用户看到 Stripe 品牌时产生疑虑
  • 订阅场景下已处理 invoice.payment_failed 事件,通知用户更新支付方式
  • 正式环境的 Dashboard 通知设置已配置(支付提醒、争议提醒等)

参考资料

  1. Stripe Checkout 官方文档 — Checkout 完整 API 参考
  2. Build a Stripe-hosted checkout page — 官方 Quick Start,含多语言代码示例
  3. How Checkout works — Checkout 工作原理详解
  4. Customize Checkout — 品牌、字段、域名等自定义选项
  5. Compare Checkout Sessions and Payment Intents APIs — Checkout Sessions vs Payment Intents 对比
  6. Test card numbers — 全部测试卡号和测试场景
  7. Stripe CLI 文档 — 本地开发环境 Webhook 调试
  8. stripe-samples/checkout-one-time-payments — 官方一次性支付示例代码