# 厂商推送测试三件套(push 族)

> 所属:push 组(前缀 `/api/v1/push`,源 `app/api/v1/push.py`) | 鉴权:**全部 Bearer** | [← 返回 API 索引](./README.md)
>
> 发送实现:`app/integrations/vendor_push.py`(荣耀 / **华为** / 小米 / OPPO / vivo 五通道,
> `send_notification()` 通用入口)。站内消息中心见 [notifications.md](./notifications.md)。
> 与 `POST /api/v1/device/push-test`(无障碍召回通道延迟自测)互补:本组面向消息中心 13 类 push 的文案/参数/通道联调。
>
> 字段命名同 notifications 族:**camelCase**。

## 链路总览

```
真实业务事件(提现回执/反馈审核/爆料通过/好友下单 已接入;奖励过期等待接)
      └→ services/notification_events(先落 notification 表,再向该用户全部已注册设备直推)
              └→ vendor_push.send_notification(vendor, token, title, body, extras)
                      extras = { type, notificationId, ...业务参数 }  ← 客户端深链 + 已读联动的钥匙
客户端点击 push → 按 extras.type 直达落地页(与站内点击一致)
              → 调 POST /notifications/read {ids:[extras.notificationId]} 同步消红点(PRD §4)
```

推送目标来源:客户端集成各厂商 push SDK 拿到 regId/token 后,通过 `POST /api/v1/device/register` 上报 `push_vendor` + `push_token`,服务端存 `device_liveness` 表。

## GET /api/v1/push/vendors — 厂商配置状态

检查 5 家厂商服务端凭据是否配齐(只读 .env,不打厂商接口)。`missingKeys` 即还要补的配置键;mock 测试不依赖任何凭据。

```jsonc
{ "vendors": [
    { "vendor": "honor",  "label": "荣耀", "configured": false, "missingKeys": ["HONOR_PUSH_APP_ID", "HONOR_PUSH_CLIENT_ID", "HONOR_PUSH_CLIENT_SECRET"] },
    { "vendor": "huawei", "label": "华为", "configured": false, "missingKeys": ["HUAWEI_PUSH_APP_ID", "HUAWEI_PUSH_APP_SECRET"] },
    { "vendor": "xiaomi", "label": "小米", "configured": true,  "missingKeys": [] },
    { "vendor": "oppo",   "label": "OPPO", "configured": false, "missingKeys": ["OPPO_PUSH_APP_KEY", "OPPO_PUSH_MASTER_SECRET"] },
    { "vendor": "vivo",   "label": "vivo", "configured": false, "missingKeys": ["VIVO_PUSH_APP_ID", "VIVO_PUSH_APP_KEY", "VIVO_PUSH_APP_SECRET"] }
] }
```

## GET /api/v1/push/templates — 13 类通知的 push 模板预览

PRD §5 的 13 条 push 文案(标题固定 ≤11 字不带变量;正文 `{var}` 为变量,示例值即 PRD 示例)。对文案、看变量名用。

```jsonc
{ "templates": [
    {
      "type": "withdraw_success",
      "category": "withdraw_assistant", "categoryLabel": "提现助手", "cardStyle": "withdraw",
      "pushTitle": "提现到账提醒",
      "pushBodySample": "¥0.50已存入您的微信钱包，点击查看到账详情",   // 用示例值渲染后的效果
      "pushBodyTemplate": "¥{amount}已存入您的微信钱包，点击查看到账详情",
      "variables": ["amount"],
      "sampleVars": { "amount": "0.50" }
    }
    // ... 共 13 条,顺序即 PRD 编号
] }
```

## POST /api/v1/push/test — 测试发送(默认 mock)

**入参(JSON)**

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `vendor` | string | ❌* | `honor/huawei/xiaomi/oppo/vivo`,中文「华为」「小米」等别名也识别;留空时用 `deviceId` 设备上报的 vendor |
| `pushToken` | string | ❌* | 厂商 push token/regId;留空则按 `deviceId` 反查已注册设备(*mock 模式两者都缺时用占位 token,只看渲染结果*) |
| `deviceId` | string | ❌ | 客户端 `DeviceId.get()` 的设备 id,用于反查 vendor+token |
| `type` | string | ❌ | 13 种类型 key 之一 → 按 PRD 模板渲染;不传且没直给文案 → 发通用测试文案 |
| `vars` | object | ❌ | 覆盖模板变量,如 `{"coins":"520","cash":"6.66"}`;缺省用 PRD 示例值 |
| `title` / `content` | string | ❌ | 直接指定标题/正文(优先于 type 模板) |
| `createNotification` | bool | ❌ | true = 同时往该用户消息中心插一条同类型未读 mock 通知,extras 带其 `notificationId` → 可闭环验证「点 push → 调 /notifications/read 消红点」(仅 type 合法时生效) |
| `mock` | bool | ❌ | **默认 true = 不真调厂商 API**,回显渲染结果;false = 真发到手机(要求该厂商凭据已配) |

**出参**

```jsonc
{
  "ok": true, "mock": true, "vendor": "huawei",
  "title": "反馈奖励已到账",
  "body": "谢谢您帮傻瓜比价变得更好，300金币已到账，还有一条给您的留言～",
  "extras": { "type": "feedback_reward", "feedbackId": "3002", "notificationId": "90017" },
  "notificationId": 90017,          // createNotification=true 时的站内 mock 通知 id
  "missingKeys": ["HUAWEI_PUSH_APP_ID", "HUAWEI_PUSH_APP_SECRET"],  // 真发前还缺的配置(真发成功时必为空)
  "vendorResponse": null            // 真发时为厂商 API 原始响应
}
```

**错误**:`400` vendor/type 非法、真发但凭据未配(detail 列缺失键);`409` 真发但拿不到 pushToken;`502` 厂商 API 返回失败(detail 带厂商原始错误)。

**真发注意**:
- 目标手机必须先装 App 且客户端已集成对应厂商 SDK、`/device/register` 上报过 token;
- vivo 未上架前走测试推送(`VIVO_PUSH_MODE=1`),目标手机需在 vivo 开放平台加入测试设备;
- 小米新设备需在开放平台把签名/包名配好,token 才有效。

## 厂商凭据怎么拿(.env 键名)

| 厂商 | 后台 | 需要的键 |
|---|---|---|
| 华为 | AGC 控制台 → 项目设置 → 常规 → 应用 | `HUAWEI_PUSH_APP_ID`、`HUAWEI_PUSH_APP_SECRET`(OAuth client_id 即 AppId) |
| 荣耀 | 荣耀开发者服务平台 → 推送服务 | `HONOR_PUSH_APP_ID`、`HONOR_PUSH_CLIENT_ID`、`HONOR_PUSH_CLIENT_SECRET` |
| 小米 | 开放平台 → 消息推送 → 应用秘钥 | `XIAOMI_PUSH_APP_SECRET`(服务端只要这个;AppID/AppKey 是客户端 SDK 用) |
| OPPO | 开放平台 → 推送服务 | `OPPO_PUSH_APP_KEY`、`OPPO_PUSH_MASTER_SECRET`(注意是**服务端 MasterSecret**) |
| vivo | 开放平台 → 推送 | `VIVO_PUSH_APP_ID`、`VIVO_PUSH_APP_KEY`、`VIVO_PUSH_APP_SECRET` |

各家发送协议差异(鉴权方式/成功码/payload 结构)封装在 `integrations/vendor_push.py`,业务侧只面对 `send_notification()`。
