付款生命周期

每一种状态、状态之间如何流转,以及哪些是终态。

状态

| 状态 | 含义 | 终态 | | --- | --- | --- | | requires_payment | 已创建。尚未收到任何款项。 | 否 | | detected | 有一笔匹配的转账出现在内存池或最新区块中。 | 否 | | confirming | 已被打包进区块,正在累积确认数。 | 否 | | paid | 已确认到配置的深度。 | 否——见下文 | | settled | 已从托管合约释放给商家。 | 是 | | underpaid | 到账金额少于要求的金额。 | 否——补足后仍可完成 | | overpaid | 到账金额多于要求的金额。 | 否 | | expired | 已过 expires_at,且没有任何款项得到确认。 | 是 | | cancelled | 你已取消该付款。 | 是 | | reversed | 链重组撤销了一笔已达到 paid 的付款。 | 是 | | partially_refunded | 部分款项已退回。 | 否 | | refunded | 全部款项已退回。 | 是 |

paid 不是终态

这是与银行卡支付网关不同的地方,而且绝不是细枝末节。

区块链可能发生重组。如果链重组的深度超过了判定该付款为 paid 的确认窗口,那么支付这笔款项的转账就不复存在——付款会变为 reversed,并发出 payment.reversed

三种诚实的处理方式:

  1. 收到 paid 就发货。最快。你是在知情的前提下承担被撤销的风险,对于低价值的数字商品,这通常是正确的选择。
  2. 收到 settled 再发货。资金已离开托管合约;那么深的链重组已不可能发生。较慢。
  3. 收到 paid 就发货,收到 reversed 再冲正。适用于任何可以撤回的东西——账户余额、订阅周期、授权许可。

不诚实的做法,是把 paid 当作最终状态,却完全没有 payment.reversed 的处理程序。做决定之前,先演练一遍:

curl -X POST $PAYGATE/v1/test/payments/{id}/reorg \
  -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" -d '{}'

少付与多付

客户会付错金额。这并不罕见。

两者都会触发事件。两者都可以在测试模式中复现。

过期

expires_at 是一个 unix 时间戳。创建付款时用 expires_in(单位:秒)来设置。

如果付款过期时已有款项处于 confirming,这笔付款不会被放弃——迟到的转账会触发 payment.late_payment,付款会被完成,而不是丢失。一个把它丢掉的网关,就是一个扣下客户的钱却不告诉任何人的网关。

读取付款

不得已时可以轮询 GET /v1/payments/{id};能订阅 Webhook 就订阅 Webhook。一笔付款背后的交易——哈希、确认数、金额——可以通过 GET /v1/payments/{id}/transactions 获取。