# 连锁总部营销与数据 API

版本：2026-10-12。适用业务：外卖。以下描述已实现的接口。

## 总部与店长如何协作

总部与店长均可开展营销。总部统一制定计划和范围，店长保留本店推广、活动入口。
总部计划支持点击推广（CPC）、满减、优惠券，可发布到全部营业门店、指定城市或指定门店。
指定门店最多500家；全国大规模计划使用全部或城市范围，后台按集合写入目标门店，不由客户端逐店请求。

总部选择范围后先预览。点击推广明确区分每店每日预算、每店累计预算、点击出价，以及全部门店参与后的预算上限。
发布时冻结门店名单与店主身份；新增店不会自动加入已发布计划。范围数量变化时必须重新预览。
同一提交编号重试不会重复发布；条款变化不得复用提交编号。

每家店由有营销管理权限的店长/所有者确认本店承担费用和数据共享后参与。普通店员不能确认。
当前执行方式为门店出资：推广使用门店自己的广告账户，满减和优惠券由门店承担优惠成本。
未确认、拒绝或不符合资格的门店不会生成优惠券、广告计划或产生扣费。
直营门店可由总部统一规划预算，但本版仍逐店确认门店出资；加盟、合伙关系不自动带来扣款权。
若未来增加总部出资，应新增总部广告账户、付款授权、预算归属和账单核对，不能直接复用门店余额。
本版不支持总部钱包统一付款、总部垫资、加盟费自动扣取或结算分账；组织关系不能代替付款授权。

总部计划创建独立的门店广告计划/活动记录，不覆盖店长原有计划和菜单售价。
同一家门店可同时参与多个总部推广计划，也可保留本店自建推广。各总部计划的广告编号、预算和累计消耗独立；店长修改本店推广预算不修改总部计划条款。
已参与不等于已投放：余额、资质、素材审核、开始时间、广告可信投放校验仍然生效。
活动复用现有优惠券及订单优惠链路；活动叠加和是否可用继续由原订单计价规则判断。

店长可拒绝、退出并停止本店计划。总部结束计划会停止新的参与，并由持久化任务每批100家停止执行，逐店更新状态；不能把“提交结束”当作全部门店已经停止。
停止失败的门店标为 `stop_failed` 并返回 `stopError`，30秒后独立重试，不阻塞其他门店。服务重启后任务继续。计划到期也会停止；广告和优惠券自身同时保留结束时间。
已经发生的费用和历史回执保留；退出不会删除历史记录。

## 商家端人员接口

使用商家登录 Bearer JWT，基路径 `/api/chain-workspace/marketing`。

| 方法与路径 | 作用 |
| --- | --- |
| GET 基路径本身（不加尾斜杠） | 分页查询当前身份可见计划 |
| GET `/stores` | 总部搜索可纳入的营业门店，支持 q、page、pageSize、cityCode |
| POST `/preview` | 校验条款，预览实际门店数量及预算上限，不创建计划 |
| POST 基路径本身（不加尾斜杠） | 发布计划；requestId 幂等，expectedTargetCount 必须与预览一致 |
| GET `/{id}/stores` | 分页读取各门店确认和实际执行状态 |
| POST `/{id}/decision` | 本店 accept / decline / withdraw |
| POST `/{id}/stop` | 总部申请结束，异步分批执行 |
| GET `/summary?days=7` | 近1至30天授权门店推广汇总 |

发布示例（日期需替换为实际未来日期）：

```json
{
  "requestId":"3b037ed0-c4b4-4b46-803f-9fa4738635e3",
  "name":"城市新品推广",
  "moduleType":"gold",
  "startDate":"2026-10-13",
  "endDate":"2026-10-19",
  "scope":{"mode":"cities","cityCodes":["310000","440100"],"merchantIds":[]},
  "terms":{"dailyBudgetFen":5000,"totalBudgetFen":30000,"bidFen":50},
  "expectedTargetCount":20
}
```

金额输入均为整数分，点击推广须满足：0＜点击出价≤每店日预算≤每店总预算≤100000元。
满减使用 `moduleType=full-reduction`，terms 为 `thresholdFen`、`discountFen`；优惠券使用 `moduleType=coupon`，另需 `stockCount`（每店1至100000张）。不同模块的金额字段不可混用。
日期按北京时间自然日解释，结束日全天有效；最多提前90天开始，持续不超过91天。
门店确认请求为 `{"action":"accept","confirmStoreCost":true,"confirmDataSharing":true}`。

## 总部系统 API 授权

1. 总管在开放平台控制台创建应用，将密钥保存在可信服务端。
2. 每家门店所有者在控制台核对应用，单独勾选 `marketing:read`，设置1至365天期限。
3. 现有 `stores:read`、`catalog:read` 授权不会自动增加营销权限。
4. 调用携带 `X-Chain-App-Key`。不要将密钥写入前端、链接、日志或二维码。
5. 每次调用核验应用、店主、门店关系、期限和权限；撤销后停止新请求访问。

| 机器接口 | 权限 | 内容 |
| --- | --- | --- |
| GET `/api/openapi/chain/v1/marketing/plans` | marketing:read | 当前授权门店涉及的总部计划及范围内计数 |
| GET `/api/openapi/chain/v1/marketing/plans/{id}/stores` | marketing:read | 计划内授权门店状态与执行编号 |
| GET `/api/openapi/chain/v1/marketing/summary?days=7` | marketing:read | 所有获该权限门店的推广汇总 |

列表支持 `q`、`page`（默认1）、`pageSize`（默认20，最大100）；计划列表支持 `kind=promotion` 或 `activity`。
返回 `items`、`total`、`page`、`pageSize`。按计划编号倒序/门店编号升序分页。
无有效营销授权返回403；错误密钥返回401；无权查看的门店和计划不会混入结果。
本版机器 API 只读，不允许机器代替店长同意费用或调用总部发布接口。

```sh
curl "$API_ORIGIN/api/openapi/chain/v1/marketing/plans?kind=promotion&page=1&pageSize=20" \
  -H "X-Chain-App-Key: $CHAIN_APP_KEY"
curl "$API_ORIGIN/api/openapi/chain/v1/marketing/summary?days=7" \
  -H "X-Chain-App-Key: $CHAIN_APP_KEY"
```

## 统计口径

- `counts.stores/pending/accepted/declined/stopped/stopFailed`：仅当前可见且仍符合总部关系和店主身份的目标门店。不可把分页行数当作全国数量。
- `counts.spendMicros`：该总部计划关联广告计划的累计消耗；1元=1000000 micros。不是每日预算，不是整个门店广告消耗。
- `/summary` 的 `scope=authorized_store_all_promotions`：授权门店范围内的全部推广，包含总部和本店自建推广，不是单个总部计划归因。
- 人员总管的汇总范围为当前仍参与总部计划且确认数据共享的门店；机器接口按独立的 marketing:read 授权，因此两个范围可能不同。对比前必须核对 `scopeStoreCount`。
- `days`：北京时间最近N个自然日，含今天。`exposures`、`clicks`、`attributedOrders` 来自既有广告日汇总投影；`spendFen`、`attributedRevenueFen` 单位为分。
- `clickThroughRate` 是汇总点击/汇总曝光，零曝光时为 null。不得平均各店转化率。
- 归因成交不等于广告带来的增量或利润。退款、无效流量等按既有投影修正流程更新。
- `sourceUpdatedAt` 为最后事实更新时间，无事件时为 null；`generatedAt` 只是本次查询时间。没有事实时不编造投放效果。
- 总部身份、普通员工身份或加入连锁关系均不会自动开放其他门店的营销数据、付款或结算权限。

## 状态与接入建议

计划：published → stopping → ended。
门店：pending → accepted / declined；accepted → stopped；停止失败进入 stop_failed，重试成功后转为 stopped。
广告实际 `deliveryStatus` 与参与状态分别返回，例如 pending_review、scheduled、active、paused_no_balance、archived。
使用 requestId/执行编号保存本地回执，分页读取，按需要缓存；不要在手机端逐店请求汇总全国数据。
接口不返回客户手机号、订单个人信息或店主凭证。
