Medusa Stripe 支付提供者 @medusajs/payment-stripe:从 2.0 到 2.20 的能力演进与源码级解析
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
@medusajs/payment-stripe 是 Medusa 官方支付模块(Modules.PAYMENT)的 Stripe 实现,负责将 Medusa 的支付会话生命周期(发起、授权、捕获、退款、Webhook 对账)映射到 Stripe PaymentIntent 与 Customer API。本文以该包的 CHANGELOG.md 为主线骨架,结合 packages/modules/providers/payment-stripe 下的源码、类型定义与测试,梳理 2.0 大版本重写以来的关键能力演进,并给出配置参数、provider 注册方式和底层调用链的完整解析。读完本文,你将掌握该模块的 8 个支付方式服务、全部配置项及其作用,以及 initiatePayment 到 Webhook 对账的完整实现路径。
一、模块概览:一个包,八种支付方式
从 package.json 可以看到,该包名称为@medusajs/payment-stripe,当前版本 2.20.1,运行时要求 Node.js >= 20,核心依赖为stripe@^15.5.0和@medusajs/framework@2.20.1(peer 与 dev 依赖同版本)。
模块入口 src/index.ts 通过ModuleProvider(Modules.PAYMENT, { services })向支付模块注册了 8 个服务:
| Provider Key | 服务类 | 对应支付方式 | 说明 |
|---|---|---|---|
stripe | StripeProviderService | 通用 Stripe 支付 | 默认 provider,payment method 由请求上下文决定 |
stripe-oxxo | OxxoProviderService | OXXO | 墨西哥现金支付,支持过期天数配置 |
stripe-bancontact | StripeBancontactService | Bancontact | 比利时本地支付 |
stripe-blik | StripeBlikService | BLIK | 波兰本地支付 |
stripe-giropay | StripeGiropayService | giropay | 德国银行转账 |
stripe-ideal | StripeIdealService | iDEAL | 荷兰本地支付 |
stripe-przelewy24 | StripePrzelewy24Service | Przelewy24 | 波兰银行转账 |
stripe-promptpay | PromptpayProviderService | PromptPay | 泰国本地支付,2.0.3 版本加入 |
这些 Key 集中定义在 src/types/index.ts 的PaymentProviderKeys常量中。其中stripe-ideal、stripe-promptpay等服务通过覆盖paymentIntentOptions的payment_method_types固定各自的支付方式,例如 PromptPay 服务固定为["promptpay"]且capture_method: "automatic"(见 src/services/stripe-promptpay.ts)。
二、配置参数:StripeOptions 完整说明
所有 provider 共享同一份选项类型StripeOptions,定义于 src/types/index.ts:
| 参数 | 类型 | 是否必填 | 默认值 | 作用 |
|---|---|---|---|---|
apiKey | string | 是 | 无 | Stripe 账户 API 密钥;缺失时validateOptions直接抛错 |
webhookSecret | string | 推荐 | 无 | 用于 Webhook 签名校验;缺失时启动阶段仅告警(见下文 2.16 演进) |
capture | boolean | 否 | false | 是否立即捕获(automatic capture),默认手动捕获(capture_method: "manual") |
automaticPaymentMethods | boolean | 否 | false | 为 true 时在 intent 请求上设置automatic_payment_methods: { enabled: true } |
paymentMethodConfiguration | string | 否 | 无 | 传入 Stripe Payment Method Configurations 的 ID(PMC ID),由 Dashboard 托管可用支付方式集合 |
paymentDescription | string | 否 | 无 | 当请求上下文未提供时,给 intent 设置的默认描述 |
oxxoExpiresDays | number | 否 | 3 | OXXO 支付过期天数,映射到payment_method_options.oxxo.expires_after_days |
asyncPaymentMethodTypes | Stripe.PaymentMethod.Type[] | 否 | 无 | 异步支付方式类型列表;未配置时所有支付方式按同步处理。异步支付方式在 Stripe 状态为pending时也允许生成订单 |
此外 PaymentIntentOptions 定义了各支付方式服务可以覆写的 intent 参数:capture_method(automatic/manual)、setup_future_usage(on_session/off_session)、payment_method_types,以及payment_method_options.oxxo.expires_after_days。
配置校验:缺失 apiKey 直接报错,缺失 webhookSecret 启动告警
src/core/stripe-base.ts 中的静态方法validateOptions定义了配置校验规则:
apiKey缺失时抛出"Required option apiKey is missing in Stripe plugin",阻止 provider 初始化;webhookSecret缺失时打印console.warn,并通过静态标志hasWarnedMissingWebhookSecret保证在 8 个 provider 服务各自被 loader 校验时只告警一次。
三、版本演进时间线:CHANGELOG 中的关键能力节点
以下是 CHANGELOG 记录的、对功能有实质影响的版本节点(依赖同步更新如@medusajs/framework的例行升级不再赘述):
2.0.0:Medusa 2.0 大版本重构
2.0.0 条目 标记为Major Changes,对应 Medusa 2.0 发布(PR #7341)。这一代将支付逻辑收敛为AbstractPaymentProvider<StripeOptions>抽象基类StripeBase,统一实现 Stripe 的 PaymentIntent / Customer / Refund / Webhook 调用,各支付方式服务仅需通过paymentIntentOptions描述差异。此前在 0.0.2 版本(PR #6700)中所有模块被打上初始版本号以支持 monorepo 联测。
2.0.3:加入 PromptPay
PR #9789(CHANGELOG 2.0.3 条目)为泰国市场新增promptpay支付方式,注册StripePromptpayService。该服务固定payment_method_types: ["promptpay"]并采用自动捕获。
2.11.x:共享支付令牌、PromptPay 注册修复、metadata 合并
- 2.11.0:
feat(payment-stripe): Allow passing shared payment token in Stripe,允许在发起支付时透传 Stripe 共享支付令牌;同时修复了StripePromptPayService未在模块 provider 中注册导致 PromptPay 无法工作的问题(这也解释了为何 2.0.3 加入 PromptPay 后仍需在 2.11.0 修正注册)。 - 2.11.1:PR #13801,发起支付时将自定义 metadata 与
session_id合并写入 intent 的 metadata,而不是覆盖。对应源码见 initiatePayment:
metadata: { ...(data?.metadata ?? {}), session_id: data?.session_id as string, }其中session_id是 Webhook 对账的关键锚点(下文详述)。
2.12.0:OXXO 支付方式与可配置过期时间
PR #13805(CHANGELOG 2.12.0 条目)新增 OXXO provider 支持,并支持配置过期时间。OxxoProviderService通过paymentIntentOptions固定payment_method_types: ["oxxo"]、capture_method: "automatic",并将oxxoExpiresDays(默认 3 天)映射为payment_method_options.oxxo.expires_after_days(见 src/services/stripe-oxxo.ts)。
2.13.2:账户持有人删除保护与外部退款同步
该版本包含两项行为修正(CHANGELOG 2.13.2 条目):
- PR #14112 修改
deleteAccountHolder实现,避免永久删除底层的 Stripe Customer。从源码看 deleteAccountHolder 当前仍调用stripe_.customers.del,社区修正是对删除语义的收敛——确保只在明确上下文下删除,避免误删用户 Stripe 档案;实施时建议结合实际使用场景评估 account holder 删除策略。 - PR #14746:处理在 Medusa 外部发起退款的情况。对应 refundPayment 中捕获
CHARGE_ALREADY_REFUNDED(ErrorCodes.CHARGE_ALREADY_REFUNDED)错误并静默放行,避免外部已退款时内部重复退款抛错。
2.16.0:webhookSecret 缺失告警与支付方式删除
(CHANGELOG 2.16.0 条目)两项改动:
webhookSecret缺失时在 provider 初始化阶段告警:此前该配置缺失会被静默接受,导致后续 Webhook 签名校验失败,依赖 Webhook 的支付流程(如 3D Secure、异步捕获)一直卡在pending。告警文案与validateOptions实现一一对应(见上文配置校验小节)。- 新增删除支付方式能力:
deletePaymentMethod通过stripe_.paymentMethods.detach将支付方式从客户档案解绑(源码),并配合listPaymentMethods(默认limit: 100列出客户全部支付方式)与savePaymentMethod(基于setupIntents保存)形成完整的保存-列表-删除闭环。
2.17.2:异步支付方式支持
PR #15085(CHANGELOG 2.17.2 条目)在payment、payment-stripe、core-flows、medusa、dashboard、js-sdk、utils、types等多个包中引入异步支付方式(async payment methods)支持,同时修复了 Webhook 中对异步支付方式检查的优雅降级。
异步支付方式(如银行转账类)的特点是:Stripe 状态为pending时订单即可被创建。其判定逻辑在 isAsyncPaymentMethod:只有options_.asyncPaymentMethodTypes中列出的类型才按异步处理。该判定同时影响两处:
getStatus中processing状态映射(源码):异步方式返回PENDING_AUTHORIZATION,否则返回PENDING;- Webhook 处理
payment_intent.created/payment_intent.processing事件时(源码):异步方式返回PENDING_AUTHORIZATION动作,同步方式返回PENDING。
2.18.0:payment_method_configuration 支持
(CHANGELOG 2.18.0 条目)新增payment_method_configuration支持:通过传入 Stripe Payment Method Configurations(PMC ID),即可在 Stripe Dashboard 上集中管理可用支付方式集合,而无需改代码。
从源码看该参数有明确的优先级与互斥逻辑(normalizePaymentIntentParameters):
if (!paymentMethodTypes?.length) { res.payment_method_configuration = (extra?.payment_method_configuration as string | undefined) ?? this.options_?.paymentMethodConfiguration }即:仅当没有显式指定payment_method_types时才应用payment_method_configuration(请求上下文优先于全局选项)。这与 stripe-base.spec.ts 中 5 个用例逐一对应:
- extra 中的
payment_method_configuration优先于 options; - 未传 extra 时使用 options 中的配置;
- 固定了
payment_method_types的专用 provider(如 iDEAL)不设置 PMC; - 同时传入
payment_method_types与 PMC 时,PMC 被忽略; - 全部未配置时 PMC 为
undefined,且默认capture_method为manual。
四、核心实现:StripeBase 与支付会话生命周期
所有支付方式服务继承自抽象类StripeBase(src/core/stripe-base.ts),它是该模块的"心脏"。
4.1 支付会话生命周期方法
StripeBase完整实现AbstractPaymentProvider<StripeOptions>的全部接口,与 Medusa Payment 模块的调用关系如下:
| 方法 | Stripe 底层调用 | 说明 |
|---|---|---|
initiatePayment | paymentIntents.create | 创建 PaymentIntent,金额换算为最小货币单位,写入session_id等 metadata |
authorizePayment | 复用getPaymentStatus | 本质是paymentIntents.retrieve并映射状态 |
capturePayment | paymentIntents.capture | 手动捕获;遇PAYMENT_INTENT_UNEXPECTED_STATE且 intent 已succeeded时视为捕获成功返回 |
cancelPayment/deletePayment | paymentIntents.cancel | 取消 intent;若 Stripe 已返回canceled状态则容错返回 |
refundPayment | refunds.create | 按最小货币单位退款;对CHARGE_ALREADY_REFUNDED幂等放行 |
retrievePayment | paymentIntents.retrieve | 查询并将金额从最小单位转回标准单位 |
updatePayment | paymentIntents.update | 金额变更时更新 intent,金额未变则直接返回当前状态 |
createAccountHolder/updateAccountHolder/deleteAccountHolder | customers.create/customers.update/customers.del | 将 Medusa 客户映射为 Stripe Customer,账单地址映射为 Stripe Shipping 地址 |
listPaymentMethods/savePaymentMethod/deletePaymentMethod | customers.listPaymentMethods/setupIntents.create/paymentMethods.detach | 客户支付方式管理 |
所有写操作都透传idempotencyKey(来自context.idempotency_key),保证网络重试下的幂等性。
4.2 金额换算:getSmallestUnit
Stripe 要求金额以最小货币单位(如分为单位)传入。工具函数 src/utils/get-smallest-unit.ts 维护了一张货币幂表:
- 幂 0(无小数位):BIF、CLP、DJF、GNF、JPY、KMF、KRW、MGA、PYG、RWF、UGX、VND、VUV、XAF、XOF、XPF;
- 幂 3(千分位):BHD、IQD、JOD、KWD、OMR、TND;
- 其余货币默认幂 2。
getSmallestUnit负责从标准单位换算到最小单位,其中对 3 位小数货币还会向上取整到最近的 10(对应部分货币的最小支付粒度约束);getAmountFromSmallestUnit则用于反向换算,Webhook 返回给订单系统的金额均经过该函数。对应的单元测试位于 src/utils/tests/get-smallest-unit.ts。
4.3 状态映射:PaymentIntent 状态 → Medusa 会话状态
getStatus(源码)是状态映射的核心,映射关系如下:
| Stripe PaymentIntent 状态 | Medusa 会话状态 | 说明 |
|---|---|---|
requires_payment_method(有 last_payment_error) | ERROR | 有失败记录 |
requires_payment_method/requires_confirmation | PENDING | 等待支付方式 |
processing(异步方式) | PENDING_AUTHORIZATION | 2.17 引入的异步语义 |
processing(同步方式) | PENDING | 处理中 |
requires_action | REQUIRES_MORE | 需要额外验证(如 3D Secure) |
canceled | CANCELED | 已取消 |
requires_capture | AUTHORIZED | 已授权待捕获 |
succeeded | CAPTURED | 已捕获 |
| 其他 | PENDING | 兜底 |
4.4 错误处理与指数退避重试
handleStripeError(源码)按错误类型分派:
StripeCardError:intent 已创建但支付失败,返回该 intent 供支付会话引用,便于 Webhook 对账;StripeConnectionError/StripeRateLimitError:结果不确定,返回retry: true;StripeAPIError:按 Stripe 官方建议视为不确定状态(indeterminate_due_to: "stripe_api_error"),依赖 Webhook 而非直接判失败;- 其他错误:抛出异常,触发会话清理。
executeWithRetry(源码)默认最多重试 3 次,采用指数退避(baseDelay * 2^(attempt-1),并叠加 0.5~1.0 的随机抖动,避免重试风暴)。
五、Webhook 对账:以 session_id 为锚的事件驱动
getWebhookActionAndData(源码)是整个异步对账链路的核心,工作流程如下:
- 签名校验:
constructWebhookEvent读取请求头stripe-signature,调用stripe_.webhooks.constructEvent校验——这正是webhookSecret缺失会导致的故障点; - 来源校验:检查 intent metadata 中的
session_id。Medusa 创建的 intent 一定会携带该字段(见initiatePayment);没有session_id的 intent 视为其他集成(共享同一 Stripe 账户)创建,直接返回NOT_SUPPORTED,杜绝跨系统误操作; - 事件分派:按
event.type映射为 Medusa 的PaymentActions:
| Stripe 事件 | Medusa 动作 |
|---|---|
payment_intent.created/payment_intent.processing | PENDING(异步方式为PENDING_AUTHORIZATION) |
payment_intent.canceled | CANCELED |
payment_intent.payment_failed | FAILED |
payment_intent.requires_action | REQUIRES_MORE |
payment_intent.amount_capturable_updated | AUTHORIZED |
payment_intent.partially_funded | REQUIRES_MORE |
payment_intent.succeeded | SUCCESSFUL |
| 其他 | NOT_SUPPORTED |
每个动作都携带session_id和换算回标准单位的金额,供支付模块定位会话并推进订单状态。
六、模块使用方式与升级注意事项
6.1 安装与配置要点
- 安装:在 Medusa 应用中通过
npm install @medusajs/payment-stripe(对应 package.json 中主入口dist/index.js)安装后,在medusa-config的支付模块 providers 中注册stripe与所需支付方式,并为每个 provider 提供apiKey(必填)与webhookSecret(强烈建议); - 路由:在 Stripe Dashboard 配置 Webhook 端点,将上述 Stripe 事件(
payment_intent.succeeded等)转发到 Medusa 的支付 Webhook 路由; - 异步方式:若使用 Klarna/Affirm 等异步支付方式,需配置
asyncPaymentMethodTypes,并留意payment_intent.created事件返回PENDING_AUTHORIZATION的语义; - 金额:所有金额在 src/utils/get-smallest-unit.ts 的货币幂表约束下自动换算,无需业务侧手工处理。
6.2 升级路径与依赖对齐
- 自 2.6.1 起(PR #11738),Medusa 移除了包版本的范围约束,
@medusajs/payment-stripe与@medusajs/framework严格同版本发布、同步升级; - 2.0.x → 2.20.x 期间,该包长期处于
Patch Changes节奏,唯一一次Major Changes是 2.0.0 的 Medusa 2.0 重写,说明其接口在 2.x 生命周期内保持稳定,升级风险主要来自@medusajs/framework的同步要求; - 若你的订单流程依赖 Webhook 完成状态推进(3D Secure、异步捕获、OXXO 等),升级后务必确认
webhookSecret已配置,否则将触发 2.16.0 引入的启动告警并导致支付流程停滞在pending。
七、总结
从 CHANGELOG 的版本时间线可以看到,@medusajs/payment-stripe的演进路径清晰:2.0 完成框架级重写(StripeBase统一抽象),2.0.3 起持续补充本地化支付方式(PromptPay、OXXO),2.11~2.18 密集完善元数据合并、账户持有人安全、异步支付语义与 Payment Method Configurations 集成,每一步都通过 stripe-base.spec.ts 等测试锁定行为。对于要在 Medusa 上落地 Stripe 支付(尤其是多地区本地支付与异步支付场景)的开发者,本文梳理的配置项、状态映射表与 Webhook 事件对应关系,可以作为排障与二次开发的直接参考。
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考