前置条件
- 你与 Waffo 已签订订阅业务合约,拿到了 Sandbox 的 API 密钥、RSA 密钥对与商户号
- 项目中
stripe-java版本不低于 24.11.0 - 你有一个可公网访问的 HTTPS 端点用于接收 Waffo 通知
- 你要路由的币种在合约范围内,可用
paymethodconfig/inquiry确认
第 1 步 安装依赖
- Maven
- Gradle
stripe-java 版本不动。 它是 provided 依赖,适配器不会替你升级或降级。waffo-java 由适配器传递引入,你不需要单独声明。
接入前请到 Maven Central 确认最新版本,并用
mvn dependency:get -Dartifact=com.waffo:waffo-java-stripe:<版本> 验证可解析后再写进 pom.xml。第 2 步 构建路由客户端
把new StripeClient(key) 换成 WaffoStripe.client(...)。适配器接收的是 Waffo 的路由配置,它本身不持有你的 Stripe 密钥。
X-Waffo-Client: waffo-stripe-java/<版本> 标识头,你不需要也不应该自己包装传输层去伪造它。
每个参数的含义、取值与默认值见下方配置参数参考。这里只强调一条选择建议:
如果你项目里已经有一个配置好超时和代理的 StripeClient,用 WaffoStripe.client(routing, existingClient) 能把这些设置一并保留,见客户端构造与 Stripe 凭据。
第 3 步 标记要转移的订阅
在原有的参数构造上加一行metadata,其余参数不动。
uiMode。Stripe 默认使用跳转式收银台,迁移工具也把未设置的值按跳转式收银台处理。这样可以同时兼容 stripe-java 24.11.x、32.x 和 33.x;不要在 32.x 或 33.x 中改用 HOSTED_PAGE,因为对应的 hosted_page 值会被判定为非跳转式收银台。
将 Stripe 的即将到期订阅无缝转移至 Waffo
先从原 Stripe 订阅读取当前已付周期的结束时间,并记为handoffAt。你仍需通过现有 Stripe 流程让旧订阅在 handoffAt 停止续费;迁移工具不会修改原 Stripe 订阅。
创建 Waffo 路由请求时,把同一个时间写入 billing_cycle_anchor,并显式设置 proration_behavior=none:
billing_cycle_anchor 会精确映射到 Waffo startTime。可预约的最大时间范围由 Waffo 后端校验,迁移工具不在本地硬编码 365 或 366 天。不要同时设置 trial_end 或 trial_period_days;这类组合会按映射失败处理。- 返回的
Session.id以wcs_开头,用client.checkout().sessions().retrieve("wcs_…")可以查回来。 session.getSubscription()返回对应的wsub_…订阅 id;用client.subscriptions().retrieve("wsub_…")会自动路由回 Waffo。- 创建成功后,把
wsub_…订阅 id 与你的业务单号一起持久化。处理 Webhook 时按这个订阅 id 查回业务记录,不要尝试从幂等键反推业务单号。 - 原生的
sub_…和cs_…id 仍然走 Stripe,两套 id 不会互相干扰。
第 4 步 翻译 Webhook 通知
在你配置的notifyUrl 端点上调用 handle(...)。SDK 会完成验签、解析、事件翻译,并生成 Waffo 用来确认本次通知已送达的响应。
返回 SDK 生成的确认响应
handle(...) 返回的 WaffoStripeWebhookResult 已包含确认响应正文。SDK 保持 Web 框架无关,因此不会直接返回 Spring 的 ResponseEntity。在 Spring 中按下面两项映射;使用其他 Web 框架时做等价映射:
这份响应表示「本次通知已被你的端点接收」。如果改成返回
"ok",Waffo 无法识别成功结果,会把同一条通知再次投递。
签名校验失败时不要执行任何业务动作。记录安全事件,然后通过订阅查询接口对需要恢复的状态做对账。
事件映射表
翻译后的事件用你平时的
event.getDataObjectDeserializer().getObject() 取数据对象,与原生 Stripe 事件一致。
以下通知不翻译,getEvent() 返回 null:
PAYMENT_NOTIFICATION——账期变更通知已经产生了invoice.paid/invoice.payment_failed,再翻译一次会重复记账。它通过getPaymentNotification()单独暴露,按paymentInfo.productName区分订阅扣款与一次性支付。SUBSCRIPTION_CHANGE_NOTIFICATION——订阅升降级,不在首期范围内。- 非终态的退款通知。
旧的
translate(body, signature) 方法仍然保留,用于源码兼容。但它只返回翻译后的 Event,拿不到上述确认响应。新接入一律用 handle(...)。第 5 步 接入立即取消
迁移工具支持通过 Stripe 的默认取消调用立即取消 Waffo 订阅,但不模拟周期末取消、指定时间取消或订阅修改。 先看两边的差异:
默认取消会调用 Waffo
subscription/cancel,再查询同一个订阅确认取消终态。如果取消结果未知,迁移工具只通过同一个 wsub_… 查询恢复结果,不会把操作转给 Stripe。若订阅仍在等待 handoffAt,取消后不会发生预定的首次扣款。
接入检查表
依赖与配置
- 依赖版本已从 Maven Central 确认可解析,不是照抄文档里的版本号
stripe-java版本不低于 24.11.0- 客户端已改为
WaffoStripe.client(...),Sandbox 配置已接入
代码改造
- 目标创建请求带
metadata.source=waffo,幂等键在调用前已持久化 - Webhook 端点使用
handle(...),并返回 SDK 生成的响应 body PAYMENT_NOTIFICATION有独立的订阅支付记录逻辑,不与一次性支付混用- 发货逻辑已从
checkout.session.completed迁移到customer.subscription.created与invoice.paid - 项目已提供
wsub_订阅的查询与默认立即取消能力,并确认不依赖周期末取消等不支持的能力 - 即将到期订阅使用同一个
handoffAt结束 Stripe 续费并设置 Waffobilling_cycle_anchor
验证
- 迁移期的
FAIL_LOUD暴露出的每一处回落都已确认并记录结论 - 项目自身的 build 与测试全部通过
- Sandbox 全链路已跑通(见下一节)
Sandbox 验证
验证必须通过你项目自己的 HTTP 接口发起,不能用适配器的内部测试代替。需要覆盖:以下是适配器的行为规则与参数详情,接入过程中遇到非预期结果时对照查阅。
配置参数参考
接入涉及三组配置,分别来自两个不同的WaffoConfig 类(同名不同包,注意区分)。
一、路由配置 com.waffo.stripe.config.WaffoConfig
适配器自己的配置,决定哪些请求走 Waffo、通知发到哪、路由不了怎么办。只有三个参数,没有别的开关。
二、OnUnsupported 枚举取值
三、Waffo 凭据 com.waffo.types.config.WaffoConfig
这是 waffo-java 的配置类,你把它交给上面的 waffoConfig 参数,适配器用它构建访问 Waffo 的客户端。
三种构造方式,任选其一:
客户端构造与 Stripe 凭据
WaffoStripe.client(...) 有三个重载。它们的区别只在于透传和回落时用哪个 Stripe 凭据,路由到 Waffo 的行为三者完全一致。
凭据优先级:每请求的
RequestOptions 密钥 > 客户端级 > 全局。
如果第二个重载无法从当前 stripe-java 版本读出客户端级凭据,客户端将无法完成初始化。请升级到受支持的版本,或改用显式传入 API 密钥的重载。
请求级参数
不支持的 Stripe 用法
首期只覆盖订阅 Checkout 的一个子集。下表列出全部不被支持的用法。 标着「转回 Stripe」的那些不影响用户支付:适配器把创建请求转给 Stripe 正常完成,用户照常付款,原因码写进返回对象的metadata.waffo_fallback_reason,同时带 waffo_routing=stripe_fallback。这两个字段名与 waffo_fallback_code 都是适配器保留的,不要在业务代码里占用。其余的会报错或不生效,需要改代码。
表里每一条都需要你对着自己的代码人工确认——扫描器只能给线索,币种、支付方式、动态拼装的参数它都判断不了。确认方法见下方提前扫出自己会踩哪几条。
反过来,已经存在的
si_…、sub_sched_…、sub_… id 都表示这个对象归 Stripe 所有,相关操作原样透传,行为不变。
提前扫出自己会踩哪几条
不用手工通读全部代码。AI 迁移技能自带一个扫描器,会把项目里每一处 Stripe 调用归到下面的分类里:
扫描器给不出结论、需要你人工追的主要是这几类:
- 参数在别的文件、工厂方法或自研封装里拼装——追到真正传给 create 的那一组值,再逐条对照上表。
- 修改/取消操作的目标 id 来源不明——顺着业务链路确认是
wsub_还是sub_:迁移工具支持查询和默认立即取消wsub_,但不支持修改;sub_原样透传 Stripe。 metadata或事件名用枚举、常量拼出来——静态扫描枚举不全,人工确认标记有没有真的打上。
扫描器基于正则与文件上下文,不是 Java 语法树分析,输出只能当清单用,不能当验收结论。
ROUTED_LIKELY 尤其要注意:它只说明没扫到明显的红线。适配器会在创建时尽量按合约预检币种,拿不到合约配置时交给 Waffo 创建接口校验;支付方式只做映射预筛,是否在合约内同样由 Waffo 创建接口校验。动态拼装的参数也只有跑起来才确定。真正的结论来自 Sandbox 验证。为防止用户资损而不回落的场景
有些情况下 Waffo 可能已经把订阅落库了,此时再转给 Stripe 建一次,用户就会被扣两次款。所以下面这些场景无论你把onUnsupported 设成什么,适配器都不会回落到 Stripe:
幂等冲突与网络未知状态。 这两种情况下 Waffo 侧可能已经创建了订阅。适配器会用原请求的同一个幂等键发起查询:
- 查到已存在的订阅 → 返回这个订阅的
wsub_会话,等同于创建成功。 - 无法确认结果 → 不支持自动回落,由你查询并确认最终状态。
版本兼容与发布认证
stripe-java 是 provided 依赖:适配器不锁定版本,由你的项目决定用哪个。
每个
waffo-java-stripe 版本发布前,都要在 14 个固定的 stripe-java 稳定版本上跑完整的确定性测试与真实 Sandbox 回归,全部通过才允许发布。版本变更记录见仓库的 CHANGELOG。
相关资源
- Stripe 迁移工具——功能介绍与适用性判断
- Webhook 签名验证——Waffo 通知的签名机制
- 幂等性——Waffo 的幂等键设计
- 错误码——查询 Waffo 错误码的含义
- GitHub 仓库——源码与更新日志