手册
已付款但无订单:改进 (Healing Paid But Unordered)
事件响应指南:客户已付费但未下订单;多方故意篮子杂乱;仔细清理重复数据。
分布式支付引擎
部分 15 的 22
一系列分布式支付架构弥合了捕获和完成之间的差距。
在上一节中,我们了解了共识工作人员如何检测偏差。本节讨论这种漂移中最烦人的形式:客户实际上已在 PSP 端付费,但系统中没有返回订单。
这种情况会产生恐慌,因为两种糟糕的解决方案似乎很诱人:立即退款(也许客户真的想要订单,开始不必要的退款重试周期)或默默地创建新订单(在不知道哪个购物车对应哪个交易的情况下冒着产生错误订单的风险)。```text PSP kaydı: Charge #789 → Succeeded, amount: 249.00 Local kayıt: (hiçbir Order veya Payment satırı yok) │ ▼ Bu paranın hangi sepete ait olduğunu belirle │ ▼ Sipariş oluştur (heal) VEYA güvenle refund et
## 第一次提到的概念```text
📦 Orphan Charge
PSP tarafında başarılı ama local sistemde hiçbir kayda bağlanamayan ücretlendirme.
📦 Multi-Intent Cart
Aynı sepet için birden fazla ödeme intent'i oluşturulmuş durum (örn. kullanıcı sayfayı iki kez yeniledi).
📦 Heal (İyileştirme)
Orphan charge'ı doğru sepete/siparişe geriye dönük olarak bağlama işlemi.
📦 Correlation ID
Bir ödeme intent'ini oluşturduğu sepete/isteğe bağlayan, ödeme akışının başında üretilen kimlik.
```孤儿费用通常是由于相关 id 在某处丢失而引起的:创建意图时记录的 id 在到达订单创建步骤之前被中断。
## 事件响应指南:第一步
当检测到孤儿费用时(通常通过对账人员或客户投诉),第一步不是采取行动,而是重新建立关联链:```text
1. PSP'nin charge metadata'sından correlation id'yi çıkar
2. Bu id ile local sistemde bir cart/intent kaydı ara
3. Kayıt bulunduysa → hangi aşamada kesintiye uğradığını belirle
4. Kayıt bulunamadıysa → refund'a yönel (heal edilecek bir hedef yok)
```关联 ID 必须始终写入 PSP 的元数据字段中(创建费用时);这是最重要的设计决策,使孤儿指控可以追溯解决。
## 多意图购物车:哪款属于哪个订单
如果客户两次打开结帐页面(选项卡刷新、双击、网络延迟后重试),则同一个购物车可能会出现两种不同的支付意图。如果两者在 PSP 方面都成功,系统面临的问题就不再是“支付失败”,而是“哪个支付获胜,另一个会发生什么”。```text
Cart #A
├─ Intent #1 → PSP: Succeeded
└─ Intent #2 → PSP: Succeeded (aynı sepet, iki farklı charge)
```这里正确的行为是锁定购物车(防止针对同一购物车创建新意图)并仅选择一个意图作为“获胜者”并自动退款给另一个意图 - 将两者附加到订单会产生双重定价。
## 仔细清理 Dedup:为什么“激进”删除是有风险的
清除孤儿费用时的最大陷阱是将重复数据删除逻辑建立在宽松的标准上,例如“相同数量、相同客户、最近时间”。此标准可能会错误地将两个单独的意向订单(客户实际上购买了两个不同的东西)视为单个订单。```text
❌ Gevşek dedup
Aynı müşteri + aynı tutar + 5 dakika içinde → tek sipariş say
✓ Sıkı dedup
Aynı correlation id VEYA aynı idempotency key → tek sipariş say
```Dedup 决策应始终基于系统本身生成的身份(相关 ID、幂等密钥);诸如数量和时间之类的间接信号只能用作额外的验证层,而不是主要标准。
## 治愈还是退款:决策标准
|状态 |正确的动作|
| --- | --- |
|发现一个不完整的购物车匹配相关 ID | Heal — 创建订单,连接付款 |
|相关 ID 与任何记录都不匹配 |退款 — 无绑定目标 |
|两个成功的意图在同一个篮子里 |治愈一个,退款另一个 |
|购物车已完成并再次付款 |退款 — 双重收费 |
每个修复流程必须生成自己的审核记录:谁/哪个流程创建了此订单、何时以及基于什么证据。该记录也用于防止将来再次发生相同的情况。
## 经常混淆的区别```text
❌ Orphan charge her zaman refund edilmeli
✓ Correlation id ile eşleşen bir hedef varsa heal etmek doğru olabilir
❌ Aynı tutar + aynı müşteri = aynı sipariş
✓ Dedup, sistemin kendi ürettiği kimliğe (correlation id) dayanmalı
❌ Multi-intent bir hata durumudur, göz ardı edilebilir
✓ Multi-intent normal bir kullanıcı davranışıdır (yenileme, çift tıklama), tasarlanmalıdır
```## 治疗和退款决定的比较
|标准|治愈 |退款 |
| --- | --- | --- |
|相关 ID 匹配 |是的 |没有或不清楚 |
|客户体验 |秩序看得见,中断感受不到 |退款,无订单 |
|风险|错配风险 |客户不满意的风险|
## 事件响应清单
1.孤儿充电的PSP元数据中有相关ID吗?否则,这是设计缺陷的迹象。
2. Dedup 决策是基于间接信号(例如数量/时间)还是基于系统生成的 ID?
3. 在多意图场景中,购物车是否会在第一次成功意图后锁定?
4. 每个治疗过程是否都会生成包含谁/何时/什么证据信息的审核记录?
5. 在做出退款决定之前,是否经过两次验证,实际上没有找到目标?
## 本文中您应该记住的内容
1. 解决孤儿费用的关键是在支付流程的一开始就安全地生成关联ID。
2. 多意图购物车是正常场景;它应该设计有篮子锁定和单一获胜意图选择。
3. 重复数据删除决策绝不应基于数量/时间等间接信号,而应始终基于身份。
4. 是否治愈或退款的问题应该是基于证据的决定,而不是条件反射的行动。
> 恐慌时期最安全的行动不是权宜之计;在找到正确的证据之前什么也不做。
在下一节中,我们将探讨这种漂移的根源:为什么在 PSP、订单和财务之间建立分布式交易(2PC)是一个陷阱,以及为什么 saga + 和解是真正的答案。
FAQ
Frequently asked questions
什么是孤儿收费?
在 PSP 端成功定价,但无法链接到本地系统上的任何记录。
什么是多用途购物车?
为同一购物车创建了多个付款意图(例如,用户刷新页面两次)。
难道“孤儿费总是应该退还”吗?
如果存在与相关 ID 匹配的目标,则修复可能是正确的。
本节修复了什么?
这种情况会产生恐慌,因为两种糟糕的解决方案似乎很诱人:立即退款(也许客户真的想要订单,开始不必要的退款重试周期)或默默地创建新订单(在不知道哪个购物车对应哪个交易的情况下冒着产生错误订单的风险)。解决孤儿费用的关键是在支付流程一开始就安全地生成关联 ID。在上一节中,我们了解了共识工作人员如何检测偏差。本节讨论这种漂移中最烦人的形式:客户实际上已在 PSP 端付费,但系统中没有返回订单。
学到的工程原理
- 解决孤儿费用的关键是在流程开始时安全地生成相关 ID。
- 重复数据删除决策应始终基于身份,而不是数量/时间等间接信号。
- 恐慌中最安全的行动是在找到正确的证据之前什么也不做。
继续阅读
继续阅读
系列中的下一个
为什么最终一致性优于分布式事务
在PSP、订单和财务之间设置2PC是一个陷阱。 Saga 和和解是分布式支付一致性的真正答案。
系列中的下一个
付款对账工人施工
扫地机如何改善漂移:虽然PSP成功,但本地注册可能会过期;如何恢复老化的 FinalizePending。
同系列
Webhook 下的乐观并发
当使用 webhook 的同步响应同时触及相同的付款时,版本令牌和租约如何解决竞争?终端支付中陈旧的读取客户端秘密...