# HTML 表单

为站点、网店或机器人接入加密货币支付的方式之一。买家点击按钮，带收款单的支付页面随即打开。

表单只能以 POST 方式提交到 `https://dash.bitsby.app/invoices/form`，包含两个字段：

| 字段        | 内容                                          |
| ----------- | --------------------------------------------- |
| `data`      | 收款单字段 JSON 的 base64url 编码             |
| `signature` | `data` 字符串的 HMAC-SHA256，64 个十六进制字符 |

每个项目以这种方式创建的待支付收款单同一时刻最多 50 张。

## 表单示例

```html
<form method="post" action="https://dash.bitsby.app/invoices/form" target="_blank">
    <input type="hidden" name="data" value="eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZm...">
    <input type="hidden" name="signature" value="f77d6da1be35bd2900e0bfed9f202b04...">
    <button type="submit">Pay</button>
</form>
```

两个值由店铺服务器在渲染页面时填入。密钥绝不出现在页面标记中。

`target="_blank"` 属性可选：加上它，购物车会留在原标签页中。

## 容器的构建方式

1. 构建包含收款单字段的 JSON 对象。
2. 将其编码为 base64url，得到 `data` 字符串。
3. 计算 `signature = HMAC-SHA256(data, formSecret)`。

签名针对整个 `data` 字符串，因此 JSON 的键顺序、缩进和 Unicode 转义风格都无关紧要。接收端也接受标准 base64，带不带填充均可。

签名后不要重新生成 `data`：哪怕只变动一个字节，签名都必须重新计算。

签名的计算方法和四种语言的现成示例见 [HTML 表单签名](./form-signature.md)。

## data 中的键

| 键             | 是否必填 | 内容                                                                                                                                                                 |
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId`    | 是       | 控制台设置中的项目 ID，uuid                                                                                                                                          |
| `amountFiat`   | 是       | 金额，1 到 100000，最多两位小数。可以是字符串 `"10.50"` 或数字 `10.5`                                                                                                |
| `currencyFiat` | 是       | 法币：USD、EUR 或 RUB                                                                                                                                                |
| `timeToPay`    | 是       | 支付期限，单位为小时：0.5、1、3、6、12。可以是字符串或数字                                                                                                           |
| `description`  | 否       | 面向买家的描述，最多 1000 个字符                                                                                                                                     |
| `serviceData`  | 否       | 店铺侧的订单标识，最多 1000 个字符。不向买家展示，但在表单源代码中可见——不要放入敏感信息                                                                             |
| `output`       | 否       | `errors`——显示校验错误的详情                                                                                                                                         |

可选键可以不写进 JSON——等同于空字符串。键的顺序任意，接收端会忽略未知的键。

值为 JSON 字符串或数字。布尔值、数组和嵌套对象不算字段值，送达时视为空字符串。

`data` 内容示例：

```json
{
    "projectId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "amountFiat": 10.5,
    "currencyFiat": "USD",
    "timeToPay": 1,
    "description": "Order #7, delivery",
    "serviceData": "order-7"
}
```

这里金额和支付期限以数字传入；字符串同样可以。可选的 `output` 完全没有设置。

## 金额格式

只允许数字和小数点，最多两位小数：`10`、`10.00`、`1000.50`。首尾空格会被去除。

其他任何格式都会被拒绝——千位分隔符、用逗号代替小数点、科学计数法、数字前的正负号、货币符号。没有四舍五入：第三位小数不会被截断，而是导致拒绝。

## 表单密钥

密钥位于控制台项目设置的表单密钥 (Form secret) 字段中，由服务器在创建项目时签发。不能自行设定值；密钥只能重新签发——例如在泄露时。

密钥必须留在店铺的服务器上。一旦出现在店面 HTML 中，签名就失去了意义。

表单密钥和 Webhook 密钥是两把不同的密钥，不要混淆。

## 订单标识

请始终填写 `serviceData`：放入自己的订单标识。该值会在[付款通知](../../webhook-url/index.md)中原样返回——店铺靠它找到订单。

填写了 `serviceData` 就能防止重复。重新提交表单或双击按钮不会创建第二张收款单：买家会被引导到已存在的待支付收款单。匹配依据的是订单字段——项目、金额、币种、描述和 `serviceData`——而不是 `data` 的字节。

每个订单的值必须唯一。值相同的话，第二位买家会进入第一位买家的待支付收款单。

空的 `serviceData` 会失去这层保护：没有可用来识别重复的依据，每次提交都会创建新收款单。重复的收款单会耗尽 50 张待支付收款单的上限，而其中一张被支付后，店铺也无从把付款对应到订单。

## 错误与调试模式

表单要经过两道检查：先查签名和容器，再查字段值。

| 情况                                                             | 服务的显示                                          |
| ---------------------------------------------------------------- | --------------------------------------------------- |
| 签名不符、容器无法读取、项目属于他人或不存在                     | 通用错误，不给出原因                                |
| 签名相符，但某个字段填写有误                                     | 通用错误。带 `"output":"errors"` 时——详细错误       |
| 两道检查都通过                                                   | 带收款单的支付页面                                  |

第一道检查在任何设置下都不会透露原因。

JSON 中的 `"output":"errors"` 键值对为第二道检查开启详情：服务会指出字段、列出允许的值，并在达到 50 张待支付收款单上限时报告。该键位于签名容器内部，无法从外部注入。

调试表单期间在 JSON 中加入 `"output":"errors"`；调试完成后删除该键并重新构建容器。

如果签名本身不符，把自己的 `data` 和签名与 [HTML 表单签名](./form-signature.md)中的参考数据对比。

## 接下来

表单创建的收款单与来自 API 或控制台的收款单处理方式相同：

* **付款确认**送达 [Webhook URL](../../webhook-url/index.md)。用 Webhook 密钥验证[通知签名](../../webhook-url/signature-verification.md)，并只依据它来发货。
* **买家返回站点**通过成功跳转地址 (Successful URL) 和失败跳转地址 (Unsuccessful URL) 配置——见[收款单生命周期](../../invoice-lifecycle.md)页面的《将买家带回店铺站点》一节。到达这些地址并不代表付款成功。
* **收款单状态和付款检索窗口**见[收款单生命周期](../../invoice-lifecycle.md)。
* **买家转错了金额**——见[付款关联与金额不一致](../../payment-matching.md)。
