Stripe 支付接入指南
Stripe 支付集成尚未开放。本文根据接入方案说明准备工作与代码示例,控制台配置与函数路由以上线后的实际实现为准。
本文介绍 Stripe Checkout 托管支付页的接入流程:服务端创建支付会话,网页跳转至 Stripe 收银台,业务侧通过 Webhook 确认支付结果。
本文不涉及 Elements 自定义收银台或 Connect 分账。托管支付页跳转不需要配置 stripePublishableKey。
适用客户端
本指南采用网页跳转方式,主要适用于 Web 客户端:
| 客户端类型 | 接入方式 |
|---|---|
| PC Web | 桌面浏览器跳转到 Stripe 托管支付页 |
| 移动 Web(H5) | 手机浏览器跳转到 Stripe 托管支付页 |
原生 App 可通过系统浏览器打开支付页,支付后的 App 回跳需自行对接。本文不覆盖 iOS/Android 原生支付 SDK,也不使用微信小程序的 wx.requestPayment 或 wx.requestVirtualPayment。
前置条件
- 已创建云开发环境,具备创建集成与部署 HTTP 云函数的权限。
- 已有可用的 Stripe 账户。账户开通地区与收款资格以 Stripe 支持地区为准。
- 已准备订单与商品数据,商品价格由服务端维护。
- 已准备支付成功与取消后的网页地址。
第一步:准备支付凭证
登录 Stripe Dashboard,选择测试环境或生产环境。
| 配置项 | 必填 | 获取位置与用途 |
|---|---|---|
stripeSecretKey | 是 | “Developers → API keys”,用于服务端调用 Stripe API,可使用 sk_test_…、sk_live_… 或具备所需权限的受限密钥 rk_… |
stripeWebhookSecret | 接收回调时必填 | 为本次集成创建 Webhook 端点后,获取该端点的 whsec_… 签名密钥,详见登记 Webhook |
stripeApiVersion | 否 | 显式指定服务端请求的 Stripe API 版本,需与 SDK 和代码使用的字段兼容 |
与其他集成的凭证准备顺序不同,需先创建集成取得回调 URL,再在 Stripe 创建对应的 Webhook 端点。随后获取该端点的 whsec_… 密钥,回填集成配置。每个端点的密钥独立,不能复用其他端点或其他集成的 Webhook 密钥。
服务端密钥与 Webhook 签名密钥用途不同,不能相互替代。密钥只保存在集成配置或服务端环境变量中,不写入网页代码。
测试环境与生产环境的密钥、Webhook 端点及支付对象分别管理,不能混用。使用受限密钥时,需授予实际调用接口所需的权限。
第二步:创建集成并配置回调
创建集成
- 进入云开发平台/集成中心,选择“Stripe 支付”。
- 填写集成名称与
stripeSecretKey,按需填写stripeApiVersion,提交创建。 - 等待平台自动生成 HTTP 云函数与回调 URL,并将凭证配置注入云函数。
- 在集成详情页查看生成的云函数,复制完整回调 URL,用于下一步登记 Webhook。
首次创建时无需手动创建或关联云函数,也无需申请回调域名。生成的函数名形如 <集成名>-<随机串>,调用时使用详情页显示的实际名称。通用创建流程可参考集成中心使用流程。
登记 Webhook
- 进入 Stripe Dashboard/Workbench/Webhooks,创建事件目标。
- 填入集成中心提供的完整 HTTPS 回调 URL。方案约定的回调路径为
/stripe/webhook。 - 选择需要接收的事件,保存端点。
- 在本次新建端点的详情中获取
whsec_…签名密钥,返回集成中心,更新stripeWebhookSecret并保存。 - 确认集成配置、端点环境与函数关联一致,再发送测试事件。
| 事件 | 业务处理 |
|---|---|
checkout.session.completed | 检查会话支付状态,已支付时执行幂等发货 |
checkout.session.async_payment_succeeded | 延迟支付成功,重新核验状态并发货 |
checkout.session.async_payment_failed | 延迟支付失败,更新订单状态,不发货 |
checkout.session.expired | 会话过期,更新对应未支付订单 |
payment_intent.succeeded | 支付成功事件,结合订单关联处理;避免与会话事件重复发货 |
payment_intent.payment_failed | 记录支付失败信息 |
charge.refunded | 核对退款金额与状态,更新业务退款记录 |
charge.dispute.created | 记录争议并通知业务人员处理 |
Stripe 使用一个 Webhook 端点接收不同类型的事件,通过 event.type 分发。回调内容是 JSON,无需执行微信支付回调中的 AES 解密。
第三步:创建 Checkout Session
以下路径采用接入方案中的示例约定,部署时以云函数实际路由为准:
| 路由 | 用途 |
|---|---|
POST /stripe/create_session | 创建 Checkout Session,返回 sessionId 与支付页 url |
GET /stripe/retrieve_session | 按 Session ID 查询支付会话 |
POST /stripe/create_refund | 方案中的退款接口,需确认函数实现与密钥权限后使用 |
POST /stripe/webhook | 接收支付与退款等事件 |
服务端创建会话
服务端先校验用户身份与订单归属,再读取订单金额或 Stripe Price ID。不要直接使用前端提交的金额创建支付会话。
以下示例依赖 stripe Node.js SDK,接收服务端已核验的订单对象。stripePriceId、quantity 与用户标识均来自业务订单,而非未校验的请求体。
const stripe = require('stripe')(process.env.stripeSecretKey, {
...(process.env.stripeApiVersion
? { apiVersion: process.env.stripeApiVersion }
: {}),
});
async function createCheckoutForOrder(order) {
if (!order.id || !order.userId || !order.stripePriceId || !order.paymentAttemptId) {
throw new Error('Missing order information');
}
if (!Number.isInteger(order.quantity) || order.quantity < 1) {
throw new Error('Invalid quantity');
}
const metadata = {
orderId: String(order.id),
userId: String(order.userId),
};
const session = await stripe.checkout.sessions.create(
{
mode: 'payment',
line_items: [{ price: order.stripePriceId, quantity: order.quantity }],
success_url: 'https://example.com/result?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cart',
client_reference_id: String(order.id),
metadata,
payment_intent_data: { metadata },
},
{ idempotencyKey: `checkout:${order.id}:${order.paymentAttemptId}` },
);
return { sessionId: session.id, url: session.url };
}
同一次支付尝试使用相同的幂等键与请求参数。重新发起支付尝试时,使用业务侧持久化的新 paymentAttemptId,并保存 Session ID 与订单的关联。
顶层 metadata 属于 Checkout Session,不会自动复制到 PaymentIntent。如需在支付事件中读取同样的订单信息,另行设置 payment_intent_data.metadata。
配置金额与跳转地址
- 使用预建商品价格时,传入
line_items[].price,如price_…。 - 使用动态价格时,在
line_items[].price_data中设置currency、unit_amount与product_data,由服务端计算金额。 unit_amount使用币种最小货币单位:20 美元传2000;500 日元传500,不能对所有币种统一乘以 100。特殊币种规则见 Stripe 币种文档。- 托管支付页使用
success_url和cancel_url。使用服务端固定地址或白名单地址,不接受任意前端回跳 URL。 {CHECKOUT_SESSION_ID}由 Stripe 替换为实际会话 ID,结果页可据此向服务端查询状态。
第四步:网页跳转支付
网页提交业务订单号,服务端创建会话后返回支付页地址。接口应配置身份认证;下例假设网页通过同源接口调用集成函数,并使用现有登录会话。
async function startCheckout(orderId) {
const response = await fetch('/stripe/create_session', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId }),
});
const result = await response.json();
if (!response.ok || !result.url) {
throw new Error(result.message || 'Unable to create checkout session');
}
window.location.assign(result.url);
}
跨域调用时,按实际域名、路由、登录方式与跨域规则调整请求。不要将 stripeSecretKey 或 stripeWebhookSecret 发送给浏览器。
success_url 只用于网页展示,不能作为发货依据。最终状态由服务端核验,Webhook 驱动订单更新与发货。
第五步:处理支付结果
验证回调来源
集成方案由网关代验签,业务函数处理转发的事件。函数应只接收可信网关请求,不能另开免鉴权公网入口接受任意事件 JSON。
自建回调或本地调试时,使用 Stripe SDK 验签。Webhook 路由必须先于 express.json() 注册,保留原始请求体。
const express = require('express');
const stripe = require('stripe')(process.env.stripeSecretKey);
const app = express();
app.post('/stripe/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = stripe.webhooks.constructEvent(
req.body,
req.headers['stripe-signature'],
process.env.stripeWebhookSecret,
);
res.json({ received: true, eventId: event.id });
} catch (error) {
res.status(400).send('Invalid webhook signature');
}
});
app.use(express.json());
此代码仅演示验签,不包含订单处理。生产环境需在返回 2xx 前完成可靠的事件入库或持久化队列投递;持久化失败时返回非 2xx,让 Stripe 重试。
更新订单与发货
- 按
event.id持久化去重,防止同一通知重复处理。 - 通过 Session ID 或业务订单号查询订单,核对用户、币种、金额与支付对象的关联。
- 收到
checkout.session.completed时,检查payment_status。paid可发货;unpaid等待后续结果;no_payment_required按业务规则处理。 - 延迟支付通过
checkout.session.async_payment_succeeded确认成功,失败事件不发货。 - 使用数据库事务或原子更新记录发货状态。以业务订单或 Session ID 保证履约幂等,避免不同事件重复触发发货。
返回 2xx 不代表业务已经到账或发货完成。事件入库后,耗时的发货与通知逻辑可异步执行,并记录失败供重试。
验证结果
在 Stripe 测试环境完成一次支付,检查:
- 创建会话接口返回 Session ID 与 Checkout URL。
- 支付后,Webhook 端点投递记录显示 2xx,云函数收到对应事件。
- 业务订单按服务端核验结果更新,发货记录只产生一次。
- 重发同一事件,不会重复发货。
- 支付取消或会话未支付,不会误标记为已支付。
不要通过生产环境密钥创建真实收费订单来验证测试链路。
常见问题
Webhook 验签失败
检查端点的 whsec_… 是否与集成配置一致、原始请求体是否被改写。网关已验签并转发后的事件不能再用改写后的 JSON 重算原始签名。
支付完成但订单未更新
在 Stripe Dashboard 的端点详情查看事件投递记录与响应码,确认事件已订阅、端点环境正确、集成函数关联正确。延迟支付还需等待异步成功事件。
本地回调调试
使用 Stripe CLI 将测试事件转发到本地回调路由:
stripe listen --forward-to localhost:3000/stripe/webhook
使用命令输出的临时 Webhook 签名密钥。它不同于 Dashboard 端点密钥,不要将其回填到线上集成。
切换测试与生产环境
分别准备服务端密钥与 Webhook 端点签名密钥。切换集成配置时,同步核对 Price ID 等支付对象的所属环境。