# Webhook URL

## 概述

该功能把已支付收款单的数据送达商户的服务器，让商户的店铺能自动处理付款，并向买家交付商品或服务。

Webhook URL 按项目单独设置，指向商户店铺内的付款处理脚本。地址必须使用 HTTPS 和域名：不接受 IP 地址，且每次投递都会校验证书。

每当收款单状态变为已支付 (Paid)，服务就向该地址发送如下格式的 POST 请求：

```json
{
   "wallet":{
      "id":"47aa71e2-07a0-482e-9172-7114d7376ba0",
      "name":"usdt-tron",
      "blockchain":"tron",
      "cryptocurrency":"usdt",
      "address":"TKbstUwMzLrfTAGL4erYb7gc7ghmHQ9zG7"
   },
   "project":{
      "id":"9deea1e2-0c08-41a3-bdc2-a34eada3892d",
      "name":"My project",
      "commissionPayer":"seller",
      "commissionRate":1
   },
   "invoice":{
      "id":"a4c9e2ee-9a03-43e5-a1a1-00caf679d16a",
      "uid":"AFhygKX21ecd",
      "createDatetime":"2024-02-26 13:29:24",
      "timeToPayDatetime":"2024-02-27 01:29:24",
      "commissionFiatUSD":0.05,
      "amountFiatUSD":5.02,
      "amountFiat":5,
      "calcAmountFiat":5.02,
      "currencyFiat":"USD",
      "description":null,
      "serviceData":null,
      "status":"paid"
   },
   "payment":{
      "id":"f986ad8d-2298-473d-982a-efbc817b975d",
      "amount":5.02,
      "hash":"74763b65e43bcc9492a6ce9a7f26fbfdbd7635aecd3454420b5e9534cba50ee6",
      "transactionDatetime":"2024-02-26 13:32:57"
   }
}
```

每次转为已支付都会发送通知——无论是服务自动找到付款，还是商户手动把付款关联到收款单。

## 请求参数

| 参数                          | 说明                                                                                                                                                                                                             |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wallet.id`                   | 钱包 ID，UUID 格式                                                                                                                                                                                               |
| `wallet.name`                 | 钱包名称                                                                                                                                                                                                         |
| `wallet.blockchain`           | 加密钱包所在的区块链                                                                                                                                                                                             |
| `wallet.cryptocurrency`       | 钱包的加密货币                                                                                                                                                                                                   |
| `wallet.address`              | 加密钱包地址                                                                                                                                                                                                     |
| `project.id`                  | 项目 ID，UUID 格式                                                                                                                                                                                               |
| `project.name`                | 项目名称                                                                                                                                                                                                         |
| `project.commissionPayer`     | 服务费由谁承担                                                                                                                                                                                                   |
| `project.commissionRate`      | 费率，单位为 %                                                                                                                                                                                                   |
| `invoice.id`                  | 收款单 ID，UUID 格式                                                                                                                                                                                             |
| `invoice.uid`                 | 收款单 ID（面向买家）                                                                                                                                                                                            |
| `invoice.createDatetime`      | 收款单创建日期和时间 (UTC)                                                                                                                                                                                       |
| `invoice.timeToPayDatetime`   | 收款单对买家有效的截止日期和时间 (UTC)。付款检索在此时刻之后再持续 1 小时——照顾慢速网络。因此已显示为过期的收款单也可能收到通知                                                                                   |
| `invoice.commissionFiatUSD`   | 服务费金额，按 `invoice.amountFiatUSD` 金额计算                                                                                                                                                                  |
| `invoice.amountFiatUSD`       | 以 USD 计的金额。创建收款单时按当时汇率由 `invoice.amountFiat` 换算。**付款时被实际收到的金额覆盖**，按付款时刻的汇率折算成 USD。`invoice.commissionFiatUSD` 中的服务费也随之重算                                 |
| `invoice.amountFiat`          | 收款单的法币原始金额，不会改变                                                                                                                                                                                   |
| `invoice.calcAmountFiat`      | 按付款时刻的加密货币汇率折算成 `invoice.currencyFiat` 币种的金额。它可能与 `invoice.amountFiat` 不同：买家未必在创建后立即支付。这段时间里，加密货币对 `invoice.currencyFiat` 的汇率可能向任一方向变动           |
| `invoice.currencyFiat`        | 法币币种                                                                                                                                                                                                         |
| `invoice.description`         | 创建收款单时填写的描述                                                                                                                                                                                           |
| `invoice.serviceData`         | 创建收款单时填写的服务数据                                                                                                                                                                                       |
| `invoice.status`              | 收款单状态                                                                                                                                                                                                       |
| `payment.id`                  | 付款 ID，UUID 格式                                                                                                                                                                                               |
| `payment.amount`              | 以加密货币计的付款金额                                                                                                                                                                                           |
| `payment.hash`                | 链上交易哈希                                                                                                                                                                                                     |
| `payment.transactionDatetime` | 链上交易的日期和时间 (UTC)                                                                                                                                                                                       |

## 通知签名

每个请求都用 Webhook 密钥签名。签名放在 `X-Timestamp` 和 `X-Signature` 请求头中，用来确认通知来自服务本身，而不是得知处理程序地址的外人。

处理订单之前先验证签名。签名机制和现成示例见 [Webhook 签名验证](./signature-verification.md)。

## 处理建议

**不要因为支付期限已过就拒绝通知**。“收款单已过期，所以付款无效”这样的检查看似合理，却会砍掉一部分真实付款：慢速网络上交易可能在期限之后才确认，商户也可以手动把付款关联到已过期的收款单。通知本身即是付款的确认。

**用原始字段** `invoice.amountFiat` **核对金额**。它是收款单开出时的金额，不会改变。`amountFiatUSD` 字段反映实际收到的金额，可能与开出的不同——既因为汇率波动，也因为手动关联付款。

**把金额当作数字解析**。末尾的零会被去掉：金额 10.00 送达时是 10，展示格式在自己一侧处理。低于 0.0001 的金额以科学计数法送达，例如 1.0e-6——标准 JSON 解析会得到正确的数字，只有手动按字符串解析才会出错。

**以幂等方式处理通知**。同一通知可能再次送达——例如脚本成功处理了付款却返回了非 2xx 状态码。发货之前，检查该 `invoice.id` 是否已经处理过。

**输出时转义值**。`invoice.description` 和 `invoice.serviceData` 字段按商户提交时的原样返回。要在 HTML 中渲染它们，请在自己一侧转义。

## 投递计划

Webhook URL 上的服务器必须返回 2xx HTTP 状态码。其他状态码、超时或连接中断都视为投递失败。

不跟随重定向：返回 301 或 302 是投递失败，而不是跳到新地址。请设置处理程序的最终地址。

建立连接限时 5 秒，整个请求限时 10 秒。处理程序超出这个时间，投递计为失败。

投递失败后，服务按以下计划重试：

* 上次投递失败后 5 分钟
* 15 分钟后
* 30 分钟后
* 1 小时后
* 3 小时后
* 6 小时后
* 12 小时后
* 24 小时后

此后停止投递尝试。

## 停用 Webhook URL

有时店铺的 Webhook 脚本正确处理了付款，却返回非 2xx 状态码。这会让服务器按上述计划频繁重试，给双方的服务器都带来额外负载。

为防止这种情况，服务设有在项目中停用 Webhook URL 的机制。要避免触发它，请按以下步骤操作：

1. 修改 Webhook 处理程序代码，让它在成功处理付款时返回 2xx 状态码，通常是 200。用任意模拟工具测试，例如 Postman。
2. 联系技术支持修正设置。
3. 在项目设置中重新启用 Webhook URL 并保存项目。
