# 服务端集成与发货通知

> 来源：https://help.gravity-engine.com/docs/gravity-dock/integration/payment-notifications
> 接收 GravityDock 支付到账通知，按原始请求体验签，校验订单并幂等发货，返回平台确认回执。
> 文档修改日期：2026-11-06




渠道支付经 GravityDock 校验后，Dock 向游戏服务端发送 `payment.paid` 通知。游戏服务端校验通知和商户订单，在事务中发放道具，提交后返回确认。

## 配置接收地址 [#配置接收地址]

1. 游戏服务端提供可直接访问的公网 HTTPS POST 地址，使用 443 端口和有效 TLS 证书。接收地址应直接处理请求，Dock 不跟随重定向。
2. 在「游戏管理 → 编辑游戏」填写「游戏发货通知地址」和独立的「通知签名密钥」，游戏服务端配置相同密钥。
3. 先完成验签、订单校验、幂等发货和回执测试，再开启「启用游戏发货通知」，用受控订单验证整条链路。

## 请求格式 [#请求格式]

请求为 POST，Content-Type 为 `application/json; charset=utf-8`。

| 请求头                  | 含义                            |
| -------------------- | ----------------------------- |
| `X-Dock-Timestamp`   | 当前投递尝试的 Unix 秒时间戳。            |
| `X-Dock-Delivery-Id` | 投递标识，重试时保持一致。                 |
| `X-Dock-Signature`   | `v1=` 加 HMAC-SHA256 小写十六进制摘要。 |

```json
{
  "version": 1,
  "event": "payment.paid",
  "delivery_id": "PS-example-delivery",
  "order_no": "PS-example-order",
  "merchant_order_no": "game-order-123",
  "channel_order_id": "channel-order-456",
  "game_id": "12345",
  "channel_id": "23456",
  "provider": "xiaomi",
  "user_id": "player-uid",
  "amount": "1.00",
  "amount_minor": 100,
  "currency": "CNY",
  "product_id": "sku-1",
  "paid_at": "2026-11-04T08:00:00Z",
  "product_count": 2,
  "product_desc": "游戏商品描述",
  "product_id": "游戏商品id",
  "product_name": "游戏商品名",
  "extra": {"role_id": "role-123", "server_id": "server-1"}
}
```

`amount_minor` 为整数分，`amount` 为十进制字符串。按整数分或精确十进制校验金额。游戏与渠道 ID 为字符串，`extra` 来自下单时的透传快照。游戏服务端应以自身订单保存的角色和区服为准。

## 签名验证 [#签名验证]

使用独立通知密钥和原始请求体计算签名。签名输入为时间戳字符串、英文句点和收到的请求体字节依次拼接。应在解析 JSON 前完成验签。

```python
import hashlib
import hmac
import json
import time

def verify_notification(raw_body: bytes, headers, secret: str):
    timestamp = headers["X-Dock-Timestamp"]
    if not timestamp.isascii() or not timestamp.isdecimal():
        raise ValueError("invalid timestamp")
    if abs(int(time.time()) - int(timestamp)) > 300:
        raise ValueError("expired notification")

    message = timestamp.encode("ascii") + b"." + raw_body
    expected = "v1=" + hmac.new(
        secret.encode("utf-8"), message, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, headers["X-Dock-Signature"]):
        raise ValueError("invalid signature")

    event = json.loads(raw_body)
    if (
        event["version"] != 1
        or event["event"] != "payment.paid"
        or event["delivery_id"] != headers["X-Dock-Delivery-Id"]
    ):
        raise ValueError("invalid notification")
    return event
```

示例采用 300 秒时间窗。实际处理时，将缺失请求头、格式错误和验签失败映射为失败响应，并保持接收端时钟同步。

## 幂等发货与回执 [#幂等发货与回执]

1. 按 `merchant_order_no` 查游戏订单，核对游戏、渠道、商品、金额、币种及绑定的玩家和角色。
2. 在同一事务内保存发货记录和道具变更，以游戏与 `order_no` 设唯一键，并确保同一商户订单只发货一次。已成功处理的重复投递应复用成功结果。
3. 发货事务提交后返回 HTTP 200 和 JSON。回执中的 `code` 为整数 0，`delivery_id` 回传当前通知标识。

```json
{"code": 0, "delivery_id": "PS-example-delivery"}
```

## 重试和对账 [#重试和对账]

通知可能重复到达。响应失败、超时或回执不匹配会触发退避重试，最多连续 8 次失败。到「玩家运营 → 订单运营」检查通知状态和投递记录。自动重试沿用首次排队的地址和密钥快照。修改地址或轮换密钥后，对仍需投递的旧记录由授权人员排障后人工重试，使用当前配置。历史已支付订单需逐笔核对，由授权人员对符合条件的订单发起补发。
