# Webhook 接收器 — 说明

路径：`pages/mall/delivery/webhook-server/webhook-receiver.js`

目的：接收承运方或 Mock Server 的 HTTP 回调（POST /webhook/express/status），将原始回文写入 `platform_express_event_raw`，并按项目现有映射更新 `platform_express_waybills` 与写入 `platform_express_tracking_events`。

环境变量（必须/可选）：
- `SUPA_URL`：Supabase REST 地址（示例 `http://192.168.1.62:18000`）
- `SUPA_KEY`：Supabase service_role 或 anon key（用于 REST 写入）
- `SUPA_USE_BEARER`（可选）：是否附加 `Authorization: Bearer <SUPA_KEY>`，默认 `false`。
  - 在一些自托管 Supabase/Kong（key-auth）环境中，**只需要** `apikey`；如果误加 Bearer 且 key 不是 JWT，可能出现 `PGRST301`（"None of the keys was able to decode the JWT"）。
- `WEBHOOK_SECRET`（可选）：与第三方共享的 HMAC-SHA256 secret，用于校验 `X-Signature`（签名为 hex）
- `WEBHOOK_REJECT_INVALID_SIGNATURE`（可选）：若为 `true`，且配置了 `WEBHOOK_SECRET`，则验签失败会直接返回 HTTP 401（默认不拒绝，只记录）。
- `WEBHOOK_PORT`（可选）：接收器监听端口，默认 `7201`（推荐用这个，便于与 push-server 共享同一份 `server/config.json`）
- `PORT`（可选）：接收器监听端口（兼容旧用法；若共享 `server/config.json` 且其中 `PORT=7301`，会导致端口冲突）

配置方式（推荐用配置文件，避免与其他服务端口冲突）：
- **同目录配置文件（推荐）**：在 `webhook-receiver.js` 同目录放置 `webhook.config.json`，启动时会自动读取。
- **显式指定配置文件**：设置 `CONFIG_FILE` / `CONFIG_PATH` 指向你的 `.env` 或 `.json`。
- **回退加载**：若未指定 `CONFIG_FILE` 且同目录无 `webhook.config.json`，会尝试读取 `server/.env` 与 `server/config.json`（由 `server/load-config.js` 负责）。

示例配置文件：仓库内提供 [server/webhook.config.json.example](server/webhook.config.json.example)，你可以复制一份到本目录作为 `webhook.config.json` 使用。

启动（PowerShell）：
```powershell
node pages/mall/delivery/webhook-server/webhook-receiver.js
```

推荐：使用同目录配置文件启动（PowerShell）：
```powershell
# 复制示例并填写真实 SUPA_KEY
Copy-Item .\server\webhook.config.json.example .\pages\mall\delivery\webhook-server\webhook.config.json

node pages/mall/delivery/webhook-server/webhook-receiver.js
```

如果你使用显式 CONFIG_FILE：
```powershell
$env:CONFIG_FILE=(Resolve-Path .\pages\mall\delivery\webhook-server\webhook.config.json)
node pages/mall/delivery/webhook-server/webhook-receiver.js
```

启动（Linux / macOS / WSL）：
```bash
export SUPA_URL='http://192.168.1.62:18000'
export SUPA_KEY='your_service_role_key'
export WEBHOOK_SECRET='your-secret' # optional
node pages/mall/delivery/webhook-server/webhook-receiver.js
```

也可以用配置文件（更适合长期运行）：
- `server/load-config.js` 会自动尝试加载：`server/.env`、`server/config.json`（以及 `CONFIG_FILE/CONFIG_PATH` 指定的文件），并把其中的键注入到 `process.env`。
- 因为接收器已在启动时 `require` 了该加载器，所以你只要把 `SUPA_URL`、`SUPA_KEY` 写进上述文件之一即可。

如果你不想与 `server/config.json` 共用 `PORT`（避免端口冲突），建议为 webhook 单独准备一个配置文件，然后用 `CONFIG_FILE` 指定：

```powershell
# 复制示例文件并填写真实 SUPA_KEY：
Copy-Item .\server\webhook.config.json.example .\server\webhook.config.json

# 指定配置文件启动（不会影响 push-server 的配置）：
$env:CONFIG_FILE=(Resolve-Path .\server\webhook.config.json)
node pages/mall/delivery/webhook-server/webhook-receiver.js
```

示例：在 `server/.env` 中写入：
```env
SUPA_URL=http://192.168.1.62:18000
SUPA_KEY=your_service_role_key
WEBHOOK_SECRET=your-secret
PORT=7201
```

测试（curl 模拟第三方推送）：
```bash
BODY='{"mailNo":"TEST_123","infoContent":"SENT","remark":"派送中","acceptTime":"2026-02-25 12:00:00","carrier":"YTO"}'
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# 如果启用了 WEBHOOK_SECRET，计算签名：
SIG=$(printf "%s%s" "$BODY" "$TS" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | awk '{print $2}')

curl -i -X POST http://localhost:7201/webhook/express/status \
  -H "Content-Type: application/json" \
  -H "X-Timestamp: $TS" \
  -H "X-Client-Id: mock_carrier" \
  -H "X-Signature: $SIG" \
  -d "$BODY"
```

> 重要：签名计算必须使用**原始请求体文本**（raw body）。接收器也会使用 raw body 进行验签；不要用 JSON 对象 stringify 后的字符串替代。

健康检查：
- `GET http://localhost:7201/health`（端口以 `PORT` 为准）

常见问题排查：
- 返回 `{ ok:false, message:'waybill not found' }`：说明 webhook 已收到请求，但在 `platform_express_waybills` 中找不到 `tracking_no`（或 `order_no`）匹配的记录。
- 返回 `502 supabase unauthorized (check SUPA_KEY/SUPA_URL)`：说明当前 `SUPA_KEY` / `SUPA_URL` 无法通过 Supabase REST 鉴权（常见于 key 填错、已失效、URL 不对）。请换成 Supabase 控制台中的真实 `service_role` key，并重启接收器。

Windows 下保持“持续监听”（后台运行）：
> 只要 Node 进程还在，webhook 就会持续监听；如果你关闭终端/窗口或按 `Ctrl+C`，进程结束就不会再监听。

```powershell
# 后台启动并把日志写到文件（推荐）
Start-Process node -ArgumentList 'pages/mall/delivery/webhook-server/webhook-receiver.js' \
  -WorkingDirectory (Get-Location) \
  -RedirectStandardOutput '.\webhook-receiver.log' \
  -RedirectStandardError '.\webhook-receiver.err.log' \
  -PassThru

# 查看是否在监听（把 7201 换成你的 PORT）
netstat -ano | findstr :7201

# 查看健康检查
Invoke-RestMethod -Uri 'http://localhost:7201/health' -Method GET | ConvertTo-Json
```

停止服务（按 PID 结束进程）：
```powershell
# 先用 netstat 找到 LISTENING 的 PID，然后结束它
Stop-Process -Id <PID>
```

依赖说明：
- 建议使用 Node.js 18+（例如你当前的 Node.js 22），已内置 `fetch`，无需安装 `node-fetch`。
- 若使用更老的 Node 且没有 `fetch`，需要安装 `node-fetch`（并保持 CommonJS 兼容）。

预期：接口返回 200 JSON {ok:true}（若未找到对应运单会返回 {ok:false, message:'waybill not found'}）。

验证写入（查看 Supabase）：
```bash
# 示例：列最近 5 条原始回文
curl -s -H "apikey: $SUPA_KEY" -H "Accept: application/json" \
  "$SUPA_URL/rest/v1/platform_express_event_raw?select=*&order=received_at.desc&limit=5" | jq .

# 查看最近轨迹事件
curl -s -H "apikey: $SUPA_KEY" -H "Accept: application/json" \
  "$SUPA_URL/rest/v1/platform_express_tracking_events?select=*&order=created_at.desc&limit=5" | jq .

# 如果你的环境已确认 Bearer 可用（不会触发 PGRST301），也可以额外加上：
# -H "Authorization: Bearer $SUPA_KEY"
```

与仓库中 Mock 实现的关系：
- `pages/mall/delivery/test/mock-service.uts` 已包含写库逻辑（`pushWebhookData`、`bindShipment`、`runScenario`）。新接收器复用了同样的入库思路，但以 HTTP/REST 的形式对外暴露。

注意事项：
- 本接收器通过 Supabase REST API 直接写表，使用 `SUPA_KEY` 时请确保权限与密钥安全（不要将 service_role key 公开到前端）。
- 如果你的 Supabase 接口部署在内网，请确保接收器能访问 `SUPA_URL`（网络/防火墙）。
- 若需要更严格的验签/重放检测/幂等，请告知，我可以把这些能力加入接收器（例如记录并比对 dedupe_key 或检查 X-Timestamp 时间窗口）。

下一步建议：
- 若你要把第三方直接接到该接收器，请把 `WEBHOOK_SECRET` 与对方约定并启用验签。
- 若需要我加重放防护或返回 4xx/5xx 更精确的逻辑，也可继续实现。

文件位置：
- [Webhook 接收器](pages/mall/delivery/webhook-server/webhook-receiver.js)

作者：自动生成（可手动调整）
