手册
语义事件而不是原始提供者数据 (Semantic Events Over Raw Provider Payloads)
提供商网关接收到的 Webhook 是否应该通过 PSP 的事件名称或语义事件(例如 PaymentCaptured/PaymentFailed)到达下游?
分布式支付引擎
部分 10 的 22
一系列分布式支付架构弥合了捕获和完成之间的差距。
我们在上一节中看到,协调器根本不应该看到 PSP SDK。超出同步调用一步后,同样的限制再次出现:来自提供商的 Webhook。
PSP webhook 通常带有自己的内部数据模型:特定于提供者的事件类型名称、特定于提供者的状态代码、特定于提供者的对象结构。将此有效负载按原样放入消息队列或事件流中意味着将提供者的模式泄漏给所有下游消费者。```text PSP Webhook { type: 'charge.succeeded', data: { object: {...} } } ▼ Provider Gateway (çeviri) ▼ Semantik event PaymentCaptured { paymentId, amount, currency }
## 第一次提到的概念```text
📦 Semantik Event
İş diliyle adlandırılmış, provider'a hiç referans vermeyen olay: PaymentCaptured, PaymentFailed.
📦 Ham Provider Payload
PSP'nin webhook body'sinde gönderdiği, kendi iç modelini taşıyan orijinal veri.
📦 Çeviri Katmanı (Translator)
Ham payload'ı okuyup semantik event'e dönüştüren, gateway içinde yaşayan bileşen.
📦 Event Sözleşmesi Sahipliği
Semantik event'in alanlarını ve anlamını kimin belirlediği; burada her zaman gateway.
````charge.succeeded` 是提供商的内部术语; `PaymentCaptured` 是您域的实际情况。两者不会同时改变:提供者可以改变事件名称,语义事件名称保持不变。
## 按原样传输原始有效负载的成本
最快的集成方法是推送消息代理而不解析 webhook 主体。它在短期内有效:消费者也解析相同的有效负载。但这会将提供者的模式更改直接传播给每个消费者。当 PSP 重命名域时,您无法控制的事件会立即破坏您的大部分系统。```text
Ham payload yayılırsa
PSP şema değişikliği → N tüketici aynı anda etkilenir
Semantik event yayılırsa
PSP şema değişikliği → sadece gateway'in çeviri katmanı güncellenir
```## 翻译层做什么、不做什么?
翻译层将提供者特定的状态代码映射到语义枚举,规范化不一致或缺失的字段,并在必要时从本地记录补充缺失的数据(例如金额、货币)。它不应该做的是做出业务决策:“为什么这次支付失败,应该做什么”这个问题的答案是协调器的工作,而不是翻译层的工作。```text
Webhook geldi
→ provider event tipini oku
→ statü eşleme tablosuna bak
→ semantik event oluştur
→ local correlation id ile eşle
→ yayınla (Outbox üzerinden)
```## 映射表是一个具体的设计工具
Provider可能有几十种事件类型;你的语义事件集应该小得多并且稳定。
|供应商活动 |语义事件 |
| --- | --- |
|收费.成功 |付款捕获|
|充电失败 |付款失败 |
|费用.争议.创建 |付款有争议 |
|付款意图.需要行动 |付款操作必填 |
该表应该在代码审查中可读;当新的提供者事件到达时,“这对应于哪个语义事件”的问题应该是单行决策,而不是分散在整个代码库中的 if-else 链。
## 订单和再次送货保证仍然有效
转换层还必须处理 Webhook 重复到达或无序到达的情况。由于网络错误,提供商可能会发送相同的 webhook 两次;生成语义事件时,这要求事件生成是幂等的——当同一个 webhook id 第二次出现时,不应重新发出相同的语义事件(或设计为下游幂等)。
## 经常混淆的区别```text
❌ Webhook = Event
✓ Webhook bir bildirim tetikleyicisidir; semantik event iş dilindeki gerçektir
❌ Ham payload'ı saklamak gereksizdir
✓ Ham payload tanılama için saklanır, ama yalnızca gateway'in kendi arşivinde
❌ Eşleme tablosu bir kere yazılır, bitmiştir
✓ Provider yeni event tipleri ekledikçe tablo canlı bir sözleşmedir
```## 使用原始通道进行语义翻译
|标准|原始通行证 |语义翻译 |
| --- | --- | --- |
|下游供应商信息|必填|不必要|
|架构更改的漏洞 |高|低|
|用于诊断的原始数据|可能会迷路|存储在网关 |
|添加新的 PSP |影响消费者|只影响映射表|
## 设计翻译层时的检查清单
1. 是否有任何下游消费者读取特定于提供商的域或状态代码?
2. 映射表是定义在一个地方还是分散在整个代码库中?
3. 原始 Webhook 有效负载是否存储在网关自己的存档中以用于诊断?
4. 当同一个webhook到达两次时,同一个语义事件是否广播两次?
5. 当新的提供程序事件类型到达时(如果尚未映射)会发生什么 - 是默默地被吞没,还是产生可见的警报?
第五个问题尤其重要:默默吞没的未知事件是生产中最隐蔽的数据丢失形式之一。
## 本文中您应该记住的内容
1. 下游永远不应该看到提供者的事件名称或状态代码。
2.翻译层是一个映射表和规范化逻辑,它不做业务决策。
3. 存储原始有效负载用于诊断,但仅限于网关自身的边界内。
4. 未知的提供者事件不应该被默默地吞噬,而应该产生一个可见的信号。
> 了解事件是否具有语义的最简单方法是从您自己的域字典中读取其名称,而不是从提供商的文档中读取。
在下一节中,我们将深入研究这些语义事件所携带的故障信息:并非每个 `PaymentFailed` 都具有相同的含义,我们需要一个故障分类法。
FAQ
Frequently asked questions
什么是语义事件?
以业务命名的事件,不涉及提供商:PaymentCaptured、PaymentFailed。
什么是原始提供商有效负载?
PSP 在其 webhook 主体中发送的原始数据,带有自己的内部模型。
“Webhook=Event”是否正确?
Webhook是一个通知触发器;语义事件是商业语言中的真理
本节修复了什么?
本节讨论为什么翻译层是一个不可忽视的责任。下游永远不应该看到提供者的事件名称或状态代码。我们在上一节中看到,协调器根本不应该看到 PSP SDK。超出同步调用一步后,同样的限制再次出现:来自提供商的 Webhook。
学到的工程原理
- 它应该看到您域的实际情况,而不是下游提供商的事件名称。
- 映射表是一份实时合约,而不是一次性代码。
- 未知的提供者事件不应该被默默地吞噬,它应该是可见的。
继续阅读
继续阅读
系列中的下一个
付款错误分类
超时、429、5xx、业务下降和基础设施错误不是一回事。每个类别都需要不同的重试策略。
系列中的下一个
SDK (ソフトウェア開発キット) を漏らさないプロバイダーの抽象化: ゲートウェイの限界
プロバイダー ゲートウェイは PSP SDK をどのように所有しているのですか。なぜチェックアウト オーケストレーターはセマンティック インターフェイスのみを参照する必要があるのでしょうか?カードとウォレットの流れは異なります...
同系列
支払い証明書と支払いステータス: 混同すべきではない理由
PSPがそう言っているのが証拠です。状態はあなたが決めるものです。これら 2 つを同じレジストリに保存しておくと、回復中にどちらを信頼すればよいかがわかります。