在讨论支付技术选型时,“Stripe 今天是什么”并不是一个能用一句话简单回答的问题。Stripe 最初以“对开发者友好的支付 API”被技术圈知晓,主要解决在线网站和移动应用如何快速接入信用卡收款。真正让它区别于传统支付网关的地方,不是某个接口写起来更短,而是它把收单、结算、风控、订阅、账单、平台分账、甚至银行账户和卡片发行都包装成了可编程接口。理解这一点,会直接影响你是否选择 Stripe、如何集成 Stripe,以及如何设计自己的支付系统。下面按这个顺序展开:产品定位、核心 API 对象、集成顺序、测试验证、问题排查、生产落地。
1. 先理解 Stripe 在支付技术栈中扮演的角色
1.1 用“支付网关”概括 Stripe 为什么不够准确
在很多开发者印象里,Stripe 就是发一个 API 请求,把钱从用户银行卡划到自己账户。这个说法不算错,但它会把 Stripe 缩小成“支付网关”。传统支付链路可以简化成:消费者卡 -> 商户网站 -> 支付网关 -> 收单行 -> 卡组织 -> 发卡行。支付网关在其中的职责更像一个“转发通道”,负责交易消息的搬运。Stripe 今天承担的职责远超这个范围。
它除了提供网关层 API,还包含收单和结算服务、商户账户体系、欺诈检测、订阅计费、账单管理、平台分账、线下终端、资金管理和卡片发行等能力。也就是说,Stripe 并不是支付链条上的单一节点,而是把多个节点整合成了可编程的金融基础设施。
这个区别很重要。选型时如果只把它当作网关,会忽略很多已经由 Stripe 提供的底层能力,比如拒付处理、3DS 验证、对账报表、风控规则。反过来,如果把它当作“万能支付系统”,又容易忽略 Stripe 的边界和约束。理解定位后,后续集成才不会重复建设,也不会给项目引入不必要的复杂度。
1.2 从商户视角看 Stripe 解决的四个问题
看产品时不需要逐个功能去背,可以按商户的资金链路来分。Stripe 实际解决四类问题:收款、业务、资金和风险。
收款是核心能力,包括网页端支付、移动端支付、线下支付以及多种支付方式。业务是把支付和业务逻辑绑定,比如订阅、账单、发票。资金是收进来的钱如何分、如何提现、如何做余额管理。风险则是识别欺诈、处理拒付、保障合规。这四类问题对应 Stripe 产品体系的不同部分,理解分类后,产品名之间的关系会清晰很多。
| 问题 | 典型场景 | Stripe 产品/能力 |
|---|---|---|
| 收款 | 独立站、App 接受付款 | Payments、Checkout、Payment Links、Terminal、Elements |
| 业务 | 订阅、按用量付费、企业发票 | Billing、Invoicing、Subscriptions |
| 资金 | 平台分账、企业资金管理、卡片发放 | Connect、Treasury、Issuing、Payouts |
| 风险 | 拒绝欺诈、处理拒付、合规 | Radar、Disputes、Sigma、合规审核 |
这个表的对应关系不是唯一的,但足够帮助新项目快速定位该看哪个产品模块。
1.3 Stripe 的边界:不是所有金融环节都由 Stripe 直接承担
在合规方面,Stripe 在很多市场是通过其金融机构合作伙伴完成实际收单和结算。具体支持的国家/地区、支持的银行卡品牌、结算时效、可用产品,都会因为主体所在地和当地法律而变化。接入前应先看 Stripe 官方支持范围,不能假设 API 能调通就等于所有资金服务都能用。
生产项目尤其要注意:Stripe 的测试模式可以模拟大部分功能,但真实结算、税务、跨境资金流动和用户协议必须由实际业务主体承担。这个边界决定了集成 Stripe 时,不只是写代码调 API,还要同步处理账号审核、商户资料、消费者条款、退款政策和账单信息。测试环境与生产环境的重大差别就体现在这些地方。
2. Stripe 产品版图:按业务场景而不是按接口记忆
2.1 线上收款核心:Payments、Checkout、Payment Links
Payments 是 Stripe 最底层也最核心的能力。它对外提供的不是一张静态收款链接,而是一套支付 API。开发者在服务端创建支付意图,在客户端收集卡信息并确认,最后通过 Webhook 获知结果。整个过程可以完全自定义 UI,适合需要品牌一致性和复杂交互的团队。
Checkout 是一个预建支付页面。开发者把商品信息和客户信息交给 Stripe,支付页面由 Stripe 托管。它适合不想处理卡表单细节、想快速上线但仍然需要一定定制能力的项目。Payment Links 更轻,直接在 Dashboard 里生成一个链接,发送给客户即可,不需要写代码。三者分别对应零开发、低开发、深度定制三种接入模式。
| 产品 | 开发量 | 品牌控制 | 适用场景 |
|---|---|---|---|
| Payment Links | 不需要开发 | 低 | 快速测试、手动收款、低频商品 |
| Checkout | 低 | 中 | 标准电商、订阅、不想维护支付页 |
| Payments + Elements | 高 | 高 | 复杂业务流、App、定制化 UX |
开发量高不代表更高级,关键看业务是否需要。很多项目使用 Checkout 已经足够,不需要为了显得“专业”而强行自定义。
2.2 平台与市场:Connect
Connect 是 Stripe 单独为平台、市场、服务商设计的产品。它的核心概念是 connected account,也就是让每一个在平台卖货或提供服务的商家都有一个 Stripe 侧的资金实体。平台负责对接客户、约定分账比例,Stripe 负责扣款、拆分资金和结算。
常见模式有直接收费、目的地收费和分离收费。比如打车平台需要向乘客收钱,再分给司机,平台还要抽取佣金,很适合用 Connect。Connect 看起来只是多了一些 API,但实际会引入平台费、账户类型、身份验证、KYC、转账状态等问题。它的集成复杂度高于普通支付,建议先从小范围试点开始,不要第一次做支付就同时引入 Connect。
2.3 订阅、账单和发票:Billing、Invoicing
如果业务是会员、SaaS 订阅或企业客户月结,Stripe Billing 可以处理循环扣款、试用期、优惠券、升级降级、以及扣款失败后的自动重试。这套逻辑不是每周期定时发一个 PaymentIntent 这么简单,因为订阅状态下会有很多动态事件:用户换了新卡、账单地址变了、试用过期、扣款日遇到周末、税务变更。Stripe 把这些变化建模成 Subscription、Price、Invoice 等对象,并通过 Webhook 通知业务系统。
Invoicing 面向对公场景,比如企业客户要求先开具账单,然后线下转账或在线支付。它和 Subscriptions 有重叠,但侧重点不同:订阅强调周期和自动化,账单强调按订单或项目出票和客户信息管理。实际项目里往往两者结合使用。
2.4 银行与资金服务:Treasury、Issuing、Capital
再往外看,Stripe 还有一些更接近银行服务的产品。Treasury 允许平台在应用内部给用户提供资金账户、余额和转账能力;Issuing 允许平台发行虚拟卡或实体卡,用于员工报销、采购、客户资金管理;Capital 则提供商户融资。这些能力合规门槛高,通常需要额外申请、审核和持续监管。普通项目没有必要在第一阶段就接入,了解即可。
从技术角度看,这些产品仍然采用统一的 Stripe API 风格,但业务逻辑完全不同。它们的共同点是把底层金融机构的服务抽象成可编程接口,让团队不需要自己对接银行核心系统。
2.5 风控、终端、数据与应用生态
Radar 是 Stripe 的欺诈检测和风控工具。默认规则覆盖常见欺诈模式,也可以通过 Radar for Fraud Teams 自定义规则,在支付被拒之前或之后评分。Terminal 则把线上支付能力延伸到线下 POS,通过 Stripe 认证的读卡器和 SDK 接受实体卡。Sigma 允许用 SQL 查询业务数据,减少手工导表。Apps 是 Dashboard 扩展,可以往 Stripe 后台加自定义功能。
对大多数开发者来说,最可能在项目里用到的还是 Payments、Checkout、Billing、Connect 和 Webhook 链路。产品版图的意义在于,不需要一开始就全部使用,但要能判断哪些问题是 Stripe 已经解决的。
3. 开发者视角下的 Stripe:先摸清核心 API 对象
3.1 PaymentIntent 是支付主流程的核心
Stripe 早期使用 Charge 对象表示一次扣款。引入 PaymentIntent 后,一次支付被建模成一个状态机,因为支付不一定马上成功。支付可能等待用户输入银行卡、等待 3DS 验证、等待银行处理,甚至被拒绝。PaymentIntent 用来跟踪这个完整过程。
创建 PaymentIntent 时,至少需要传 amount、currency 和 payment_method_types。金额必须使用最小货币单位,并且是整数。如果商品价格是 19.99 美元,服务端应传 1999,不是 19.99,也不是字符串 "19.99"。
const Stripe = require('stripe'); const stripe = new Stripe('sk_test_xxx'); async function createPaymentIntent(amountCents, currency = 'usd') { const paymentIntent = await stripe.paymentIntents.create({ amount: amountCents, currency, payment_method_types: ['card'], }); return paymentIntent; }创建后需要把 paymentIntent.client_secret 返回给前端,前端用 Stripe.js 的 confirmCardPayment 方法确认付款。client_secret 不是密钥,可以出现在客户端;secret key 则绝不能离开服务端。
PaymentIntent 的状态通常是:requires_payment_method 表示还没有可用的支付方式;requires_confirmation 表示等待确认;requires_action 表示需要用户去完成 3DS 等额外验证;processing 表示银行正在处理;succeeded 表示成功;canceled 或 requires_payment_method 表示取消或失败。项目里不要只判断成功,还要处理 requires_action,否则 3DS 用户会卡在支付页。
3.2 Customer 与 PaymentMethod
PaymentIntent 描述的是“一次交易”,Customer 描述的是“付款人”,PaymentMethod 描述的是“付款方式”。它们相互独立。把银行卡保存到 PaymentMethod 后,可以在下次支付时复用,也可以在 Customer 下管理多张卡。
卡数据通过 Stripe.js 或 SDK 收集,Stripe 返回一个 token 或 PaymentMethod ID,这样原始卡号不会进入你的服务器。这一点对 PCI 合规很关键。
实际项目中,可以先创建 Customer,再把 PaymentMethod 挂到 Customer 上,最后在 PaymentIntent 里直接使用 customer 和 payment_method。这样避免每次支付都让用户重新输卡。
3.3 Subscription 和 Invoice:周期支付不是简单循环扣款
不要用定时任务去反复调用 Charge 类接口来模拟订阅。订阅牵扯的状态很多,例如计费周期、试用、升降级、抵扣金额、税费、宽限期、扣款失败的自动重试。
Stripe 把订阅建模为 Subscription 对象,每个计费周期生成一个 Invoice,最终由 payment_intent 完成扣款。业务系统只需要监听相应事件,而不是自己维护一套循环调度。
例如客户订阅 Pro 计划,第 2 个月扣款失败后 Stripe 会按 dunning 规则自动重试,并触发 invoice.payment_failed 事件。如果自己写循环扣款,就无法低成本地复现这套容错逻辑。
3.4 Webhook 是异步事件的入口
支付结果不能完全依赖前端返回,因为浏览器可能被关闭、银行处理延迟、3DS 页面超时。正确做法是让客户端显示一个“处理中”状态,服务端通过 Webhook 接收 Stripe 发送的异步事件,再更新订单、开通权限、发送通知。
Stripe Webhook 是服务端 POST 请求,带 Stripe-Signature 头。收到后必须校验签名,防止伪事件。SDK 通常提供构造事件的方法:
const payload = req.body; const sig = req.headers['stripe-signature']; const webhookSecret = 'whsec_xxx'; let event; try { event = stripe.webhooks.constructEvent(payload, sig, webhookSecret); } catch (err) { return res.status(400).send(`Webhook Error: ${err.message}`); } switch (event.type) { case 'payment_intent.succeeded': // 更新订单状态 break; case 'payment_intent.payment_failed': // 记录失败并通知用户 break; default: // 不需要处理的事件 }要注意:在 Express 中,Webhook 路由要使用原始请求体,不能使用已经 JSON.parse 后的 body,否则签名校验会失败。
常见事件类型和业务动作可以整理成表:
| Event | 业务含义 | 典型动作 |
|---|---|---|
| payment_intent.succeeded | 支付成功 | 更新订单、开通服务 |
| payment_intent.payment_failed | 支付失败 | 记录失败、提示用户 |
| charge.refunded | 退款完成 | 更新退款状态 |
| customer.subscription.updated | 订阅状态变化 | 同步套餐和权限 |
| invoice.payment_failed | 发票扣款失败 | 启动重试、通知用户 |
把事件名和业务动作映射关系做成表,比在代码里到处 switch 更容易维护。
4. 从零集成 Stripe 的推荐顺序:先跑通最小路径再扩展
4.1 账号与密钥:先分清两类 Key
注册 Stripe 后会得到 publishable key 和 secret key,两者都有 test 和 live 两种模式。publishable key 以 pk_ 开头,可以暴露在客户端;secret key 以 sk_ 开头,只能放在服务端或后端环境变量。test 模式使用 sk_test_xxx,不会产生真实扣款;live 模式使用 sk_live_xxx,会有真实资金流。
| Key 类型 | 示例前缀 | 能否暴露客户端 | 用途 |
|---|---|---|---|
| publishable | pk_test_xxx / pk_live_xxx | 可以 | 前端初始化 Stripe.js、创建 PaymentMethod |
| secret | sk_test_xxx / sk_live_xxx | 不可以 | 服务端创建 PaymentIntent、管理订阅、查询数据 |
如果把 sk_live 提交到 GitHub、写在移动端或前端代码里,别人拿到后就能以你的商户身份创建退款、查看交易数据、甚至修改账户信息。一旦泄露,要在 Dashboard 里立即轮换密钥,而不是简单删除公钥。
4.2 选型:先回答三个问题
第一,是一次性收款还是周期订阅。一次性收款可以直接用 Payment Links、Checkout 或 PaymentIntent;周期订阅优先看 Billing。第二,是否需要平台分账。如果多个商户共用一套收款,需要 Connect;只是单商户收款,不必引入 Connect。第三,是否需要自定义支付 UI。需要深度品牌和交互控制,使用 Payments 加 Elements;时间紧且可接受托管页面,使用 Checkout;不想开发后台,用 Payment Links。
这组判断决定整个项目结构。不要在需求还没清楚时就开始写支付代码,支付组件之间的切换成本比一般业务模块高。
4.3 最小集成流程:四步走
以自定义 UI 的一次性支付为例。第一步,服务端创建 PaymentIntent,把 client_secret 返回前端。第二步,前端用 Stripe.js 加载 publishable key,创建 PaymentMethod 并调用 confirmCardPayment。第三步,客户端跳转到成功或失败页。第四步,服务端 Webhook 收到 payment_intent.succeeded 后,把本地订单状态改成 paid。这四步构成一个最小闭环。
服务端示例:
app.post('/create-payment-intent', async (req, res) => { const paymentIntent = await stripe.paymentIntents.create({ amount: 1999, currency: 'usd', }); res.json({ clientSecret: paymentIntent.client_secret }); });前端示例:
const stripe = Stripe('pk_test_xxx'); const { clientSecret } = await fetch('/create-payment-intent').then(r => r.json()); const { error } = await stripe.confirmCardPayment(clientSecret, { payment_method: { card: cardElement, billing_details: { name: 'Customer Name' }, }, }); if (error) { // 展示错误 }cardElement 由 Stripe Elements 创建,完整代码需要先初始化 Elements,这里只展示核心调用。前端拿到 error 后要区分:requires_action 是让用户去 3DS,不是最终失败;继续监听 payment_intent.succeeded 才是可靠结果。
4.4 测试环境怎么验证
Stripe 提供 test mode,使用测试卡号不会发生真实扣款。不同卡号可以模拟不同结果:成功、需要 3DS、余额不足、被拒付。本地开发可以使用 Stripe CLI 的 listen 命令把 Webhook 转发到 localhost,也可以使用 trigger 命令模拟事件。具体命令以安装后的帮助信息为准。
| 测试场景 | 卡号 | 结果 |
|---|---|---|
| 支付成功 | 4242 4242 4242 4242 | 付款成功 |
| 需要 3DS | 4000 0025 0000 3155 | 进入验证流程 |
| 支付被拒绝 | 4000 0000 0000 0002 | 付款被拒绝 |
| 余额不足 | 4000 0000 0000 9995 | 余额不足错误 |
测试时不要只看“支付成功”,还要测用户取消、卡被拒绝、Webhook 重复投递、服务重启后事件是否幂等处理。这些异常分支才是生产事故的主要来源。测试后如果订单状态没有变成 paid,优先看 Dashboard 的 Events 记录和本地 Webhook 日志。
5. 集成和上线阶段常见的五类问题
5.1 Webhook 收不到事件
现象是前端支付成功,后台却没有更新订单。可能原因:Dashboard 里没有配置 Webhook endpoint;endpoint 没有响应 2xx;签名校验使用了错误的 secret;本地环境下 Stripe 无法访问到 localhost。
检查方式:先到 Dashboard Events 里找对应事件,看状态和最后响应码;再用 Stripe CLI 的 listen 转发到本地,看请求是否到达。修复方式:配置正确的 endpoint,在路由里使用原始请求体,收到事件后立即返回 2xx,耗时操作放到异步任务中。事件处理要保证幂等,因为 Stripe 可能多次发送同一事件。
5.2 密钥泄露
现象是 sk_live_xxx 出现在 GitHub、前端 bundle、日志或截图里。原因通常是开发时把密钥写死在代码中,或者截图外发。后果是攻击者可以查询客户、发起退款,甚至获取账户余额。
处理方式:立即在 Dashboard 轮换 secret key,同时检查近 24 小时 API 日志中是否有可疑操作。预防方式:密钥只存在服务端环境变量,使用密钥管理服务,不入代码仓库,不打印日志,给不同环境分配独立密钥。
5.3 金额和币种精度错误
现象是用户看到 19.99 美元,服务端创建 PaymentIntent 时传入 19.99,Stripe 返回错误,或者支付金额多一分少一分。原因在于 Stripe 使用最小货币单位整数;美元是两位小数,日元是零位小数。如果代码用浮点数计算金额,0.1 + 0.2 这类问题会在对账时暴露。
解决方式:金额在服务端统一用整数分存储,不要在前后端传递浮点金额。需要展示时再格式化;不同币种小数位不同,以 Stripe 的 Currency API 或商品配置为准。
不要用浮点计算支付金额。支付金额不是数学近似,而是精确账目。一旦出现 19.989999,有的网关可能会拒付,对账也会不平,排查成本非常高。
5.4 重复 Webhook 导致重复处理
现象是同一笔订单收到多次 payment_intent.succeeded,库存扣了两次。原因包括网络重试、Webhook 端点响应慢、没有返回 2xx 导致 Stripe 重发。
解决方式:在业务表里保存 payment_intent_id 作为唯一键,处理前先检查是否已存在;事件处理器要兼容重复投递。可以将 event.id 记录到已处理事件表,处理前先查重。不要假定同一个 event 只会来一次。
5.5 拒付和风控信号
现象是订单支付成功,但几天后客户发起拒付,资金被退回,订单已经发货造成损失。原因在于支付成功不等于风控结束,银行允许持卡人发起 dispute。
处理方式:开启 Webhook 监听 charge.dispute.created,及时准备证据材料,在限定时间内应答,否则款项会被退回。Radar 可以帮助在消费前识别高风险交易,但不能保证零拒付。生产项目要建立拒付工单流程,记录订单、物流、IP、客户沟通证据。
6. 从“能支付”到“生产可用”:最佳实践和扩展方向
6.1 学习环境和生产环境的差别
很多项目在测试模式跑通后,直接把密钥换成 live 就上线,忽略 Webhook 和幂等,这会在真实流量下暴露大量问题。上线前应该逐项检查。
| 维度 | 学习/测试 | 生产 |
|---|---|---|
| 密钥 | sk_test_xxx | sk_live_xxx,服务端环境变量 |
| 金额 | 测试卡或少量金额 | 真实交易,必须精确到分 |
| Webhook | CLI 本地转发 | HTTPS endpoint,签名校验,监控告警 |
| 订单处理 | 可手动改库 | 幂等、事务、对账 |
| 风控 | 可忽略 | 配置 Radar 规则,处理拒付 |
| 日志 | 可详细打印 | 脱敏,防止卡信息和密钥泄露 |
6.2 发布前检查清单
下面的清单可以直接用于发布评审:
- 环境和密钥:是否使用独立 live key?是否开启密码和权限控制?
- Webhook:是否创建生产 endpoint?是否验证签名?事件处理是否幂等?
- 支付流程:是否处理 requires_action?是否处理 payment_failed?是否有取消和退款路径?
- 对账:是否有本地支付流水?是否有日终核对脚本?
- 日志与告警:是否记录 event.id 和 payment_intent.id?是否有事件处理失败告警?
- 安全:是否确认前端不会接触 secret key?是否对日志