Skip to main content
本文假设你已经读过 Stripe 迁移工具,确认这条迁移路径适合你的项目。下面先讲怎么改,再讲背后的规则。

前置条件

  • 你与 Waffo 已签订订阅业务合约,拿到了 Sandbox 的 API 密钥、RSA 密钥对与商户号
  • 项目中 stripe-java 版本不低于 24.11.0
  • 你有一个可公网访问的 HTTPS 端点用于接收 Waffo 通知
  • 你要路由的币种在合约范围内,可用 paymethodconfig/inquiry 确认

第 1 步 安装依赖

保持你项目里现有的 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 密钥
适配器会自动在每个发往 Waffo 的请求上打 X-Waffo-Client: waffo-stripe-java/<版本> 标识头,你不需要也不应该自己包装传输层去伪造它。 每个参数的含义、取值与默认值见下方配置参数参考。这里只强调一条选择建议:
迁移期先用 FAIL_LOUD 默认的 FALLBACK 会静默把无法路由的请求转给 Stripe,迁移初期你反而看不出哪些订阅没切过去。先用 FAIL_LOUD 把问题全部暴露出来,逐条确认并处理完,再切回 FALLBACK 作为生产环境的兜底。
如果你项目里已经有一个配置好超时和代理的 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_endtrial_period_days;这类组合会按映射失败处理。
幂等键不能超过 32 个字符。你必须在调用前生成并持久化,重试时复用同一个。适配器会把这个键作为 Waffo 的 subscriptionRequest,用于确认创建结果和防止重复订阅。不要直接传入超过 32 个字符的业务单号:当前版本会把长键转换为不可逆的 32 字符摘要,Webhook 无法用摘要还原原值。如果现有业务单号更长,请另外生成一个不超过 32 个字符的稳定关联键,并持久化它与业务单号的关系。不要依赖 stripe-java 自动生成幂等键。适配器会在 Stripe 网络层生成自动键之前拦截请求,这个自动键不会成为 Waffo 的 subscriptionRequest。如果没有在 RequestOptions 中显式传入键,商户也无法持久化并在下一次调用中复用它。
创建成功后:
  • 返回的 Session.idwcs_ 开头,用 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——订阅升降级,不在首期范围内。
  • 非终态的退款通知。
依赖 checkout.session.completed 发货的项目必须改造。 适配器不翻译这个事件。请把订阅激活与权益发放迁移到 customer.subscription.createdinvoice.paid,并用业务幂等防止重复发放。这是迁移中最容易被漏掉的一处。
旧的 translate(body, signature) 方法仍然保留,用于源码兼容。但它只返回翻译后的 Event,拿不到上述确认响应。新接入一律用 handle(...)

第 5 步 接入立即取消

迁移工具支持通过 Stripe 的默认取消调用立即取消 Waffo 订阅,但不模拟周期末取消、指定时间取消或订阅修改。 先看两边的差异: 默认取消会调用 Waffo subscription/cancel,再查询同一个订阅确认取消终态。如果取消结果未知,迁移工具只通过同一个 wsub_… 查询恢复结果,不会把操作转给 Stripe。若订阅仍在等待 handoffAt,取消后不会发生预定的首次扣款。
invoice_now=trueprorate=true 等额外计费语义的取消会直接报错。Waffo 当前也不支持与 Stripe 周期末取消等价的能力。如果你的产品依赖 cancel_at_period_end、指定时间取消、取消后恢复或 Subscription.update("wsub_…"),请把对应链路保留在 Stripe。

接入检查表

依赖与配置

  • 依赖版本已从 Maven Central 确认可解析,不是照抄文档里的版本号
  • stripe-java 版本不低于 24.11.0
  • 客户端已改为 WaffoStripe.client(...),Sandbox 配置已接入

代码改造

  • 目标创建请求带 metadata.source=waffo,幂等键在调用前已持久化
  • Webhook 端点使用 handle(...),并返回 SDK 生成的响应 body
  • PAYMENT_NOTIFICATION 有独立的订阅支付记录逻辑,不与一次性支付混用
  • 发货逻辑已从 checkout.session.completed 迁移到 customer.subscription.createdinvoice.paid
  • 项目已提供 wsub_ 订阅的查询与默认立即取消能力,并确认不依赖周期末取消等不支持的能力
  • 即将到期订阅使用同一个 handoffAt 结束 Stripe 续费并设置 Waffo billing_cycle_anchor

验证

  • 迁移期的 FAIL_LOUD 暴露出的每一处回落都已确认并记录结论
  • 项目自身的 build 与测试全部通过
  • Sandbox 全链路已跑通(见下一节)

Sandbox 验证

验证必须通过你项目自己的 HTTP 接口发起,不能用适配器的内部测试代替。需要覆盖:
以下是适配器的行为规则与参数详情,接入过程中遇到非预期结果时对照查阅。

配置参数参考

接入涉及三组配置,分别来自两个不同的 WaffoConfig 类(同名不同包,注意区分)。

一、路由配置 com.waffo.stripe.config.WaffoConfig

适配器自己的配置,决定哪些请求走 Waffo、通知发到哪、路由不了怎么办。只有三个参数,没有别的开关。

二、OnUnsupported 枚举取值

这个参数的管辖范围是有限的。 它只对「创建请求在发出前或收到明确拒绝后就能判定无法路由」的五类情况生效:命中红线、Waffo 明确拒绝、支付方式不支持、字段映射失败、未配置 Waffo 客户端。不影响幂等冲突与网络未知状态的处理——那两种情况由独立的处理逻辑负责,见为防止用户资损而不回落的场景

三、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 所有,相关操作原样透传,行为不变。
迁移期间应保持 onUnsupported=FAIL_LOUD。标着「转回 Stripe」的那一类会改为抛异常,便于逐条确认;全部确认后再切回 FALLBACK 作为生产兜底。见配置参数参考

提前扫出自己会踩哪几条

不用手工通读全部代码。AI 迁移技能自带一个扫描器,会把项目里每一处 Stripe 调用归到下面的分类里: 扫描器给不出结论、需要你人工追的主要是这几类:
  • 参数在别的文件、工厂方法或自研封装里拼装——追到真正传给 create 的那一组值,再逐条对照上表。
  • 修改/取消操作的目标 id 来源不明——顺着业务链路确认是 wsub_ 还是 sub_:迁移工具支持查询和默认立即取消 wsub_,但不支持修改;sub_ 原样透传 Stripe。
  • metadata 或事件名用枚举、常量拼出来——静态扫描枚举不全,人工确认标记有没有真的打上。
如果你的项目里同时存在候选迁移的订阅创建,以及 SubscriptionItemSubscriptionSchedule 调用,必须先确认这些调用属于哪条创建链。依赖多 item、按比例分摊或计划表阶段的整条链路应当整体保留在 Stripe。归属没确认清楚之前,不要给候选的创建请求加 source=waffo
扫描器基于正则与文件上下文,不是 Java 语法树分析,输出只能当清单用,不能当验收结论ROUTED_LIKELY 尤其要注意:它只说明没扫到明显的红线。适配器会在创建时尽量按合约预检币种,拿不到合约配置时交给 Waffo 创建接口校验;支付方式只做映射预筛,是否在合约内同样由 Waffo 创建接口校验。动态拼装的参数也只有跑起来才确定。真正的结论来自 Sandbox 验证

为防止用户资损而不回落的场景

有些情况下 Waffo 可能已经把订阅落库了,此时再转给 Stripe 建一次,用户就会被扣两次款。所以下面这些场景无论你把 onUnsupported 设成什么,适配器都不会回落到 Stripe: 幂等冲突与网络未知状态。 这两种情况下 Waffo 侧可能已经创建了订阅。适配器会用原请求的同一个幂等键发起查询:
  • 查到已存在的订阅 → 返回这个订阅的 wsub_ 会话,等同于创建成功。
  • 无法确认结果 → 不支持自动回落,由你查询并确认最终状态。
Waffo 明确拒绝。 即使是明确拒绝,适配器也会先用同一个幂等键查询一次,只有在确认订阅不存在时才按原因码回落。查到已有订阅就路由到它;任何瞬时或不确定的查询结果都不支持自动回落。 这两条规则的取舍是一致的:宁可暂停本次创建并先确认状态,也不能让用户被重复扣款。

版本兼容与发布认证

stripe-java 是 provided 依赖:适配器不锁定版本,由你的项目决定用哪个。 每个 waffo-java-stripe 版本发布前,都要在 14 个固定的 stripe-java 稳定版本上跑完整的确定性测试与真实 Sandbox 回归,全部通过才允许发布。版本变更记录见仓库的 CHANGELOG

相关资源