Files
webman-buildadmin-mall/docs/PlayX-对接文档(积分商城).md
2026-03-30 11:47:32 +08:00

537 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 0. 交付说明(给 PlayX
- **交付物**:本文件(接口清单 + 业务流程 + 联调验收清单)。
- **建议联调顺序**Token 验证(远程 PlayX 或本地 `verify_token_local_only`)→ 每日推送 → 领取 → 红利发放 → 提现入账 → 实物后台处理。
- **约定**:接口 URL、字段最终表、签名细节以 PlayX 提供的最终口径为准;本文档负责把流程、幂等、重试与最小字段集合先对齐。
## 1. 文档目的与范围
本文档用于 PlayX 与积分商城Points Mall联调对接。范围仅包含
- 前端PlayX 以内嵌 Iframe 打开商城 H5使用 postMessage 传递 token/session。
- 后端:商城后端独立部署;与 PlayX 后端通过 REST API 通讯。
- 数据同步:仅 PlayX 每日 Cron 推送T+1玩家数据到商城用于计算“待领取积分/今日可领取上限”。
- 发放方式:商城在红利兑换/提现(回平台余额)下单后,直接调用 PlayX API 发放/入账PlayX 侧每 10 分钟 Cron 执行 5.9 adjustment/最终入账。
不在本文档范围内:
- 任何实时 webhook充值、外部积分、流水等
- 会员端“同步额度/同步流水”按钮触发的对接链路。
## 2. 系统边界与调用方向
```mermaid
flowchart LR
PlayXFrontend["PlayXFrontend"] -->|"postMessage(token/session)"| MallFrontend["MallFrontend(Iframe)"]
MallFrontend -->|"API(商城后端)"| MallBackend["MallBackend"]
MallBackend -->|"TokenVerificationAPI"| PlayXBackend["PlayXBackend"]
PlayXBackend -->|"DailyPushAPI(T+1)"| MallBackend
MallBackend -->|"BonusGrantAPI/BalanceCreditAPI"| PlayXBackend
```
> 当 **`playx.verify_token_local_only=true`** 时「Token 验证」一步在商城内完成,**不经过** `PlayXBackend` 的 Token Verification API详见 **§4.1**。
## 3. 关键业务对象与状态机
### 3.1 资产口径(最小集合)
- **LockedPoints待领取积分**:由 PlayX 每日推送的“昨日输赢净额”在商城端按规则计算得到,未领取前不可消费。
- **AvailablePoints可用积分**:领取后可用于兑换/提现的积分余额。
- **TodayLimit今日可领取上限**:由 PlayX 每日推送的“昨日总存款”按规则计算得到。
- **TodayClaimed今日已领取**:当日累计领取量(用于进度条与上限控制)。
### 3.2 订单类型
- **BONUS**:红利兑换
- **PHYSICAL**:实物兑换
- **WITHDRAW**:提现回平台余额(非现金出款)
### 3.3 统一订单状态
- **PENDING处理中**:订单已创建,等待发放/审核/发货等后续处理
- **COMPLETED已发放**:红利到账或提现入账完成
- **SHIPPED已发货**:实物已发货,包含物流公司与单号
- **REJECTED已驳回**:失败或人工拒绝;积分需退回(退回规则见 6.2
## 4. 端到端流程6 条)
### 4.1 登录鉴权Iframe + token
> **接口与字段细节**以代码为准完整说明见同目录《PlayX-接口文档.md》§3 H5、§3.2 `temLogin`、§3.3 `verify-token`)。
#### 4.1.1 身份与数据模型(商城侧)
- **商城用户**:表 `mall_user`H5 临时登录、后台创建等均落此表)。
- **PlayX 资产扩展**:表 `mall_playx_user_asset`,与 `mall_user` **一对一**`mall_user_id``playx_user_id` 均唯一)。
- **业务侧用户标识**:对外接口中的 `user_id`(字符串)在多数场景下即 **`playx_user_id`**PlayX 玩家 ID
- 若用户仅通过商城 **临时登录** 进入、尚无 PlayX 正式 ID商城会生成占位 ID形如 **`mall_{mall_user.id}`**,与每日推送中的真实 `user_id` 区分(避免与纯数字 ID 混淆)。
- **H5 调业务接口时**:服务端内部统一解析为 **`mall_user.id`**再查资产与订单解析规则见《PlayX-接口文档》§3.1)。
#### 4.1.2 模式 A联调 PlayX生产/预发,远程校验 token
1. 用户在 PlayX 内打开积分商城入口iframe
2. PlayX 前端通过 postMessage 将 **PlayX 下发的 token**(及必要上下文)传给商城 H5。
3. 商城 H5 调用商城后端 **`POST /api/v1/playx/verify-token`**,由商城向 PlayX 的 **Token Verification API**`playx.api.base_url` + `playx.api.token_verify_url`)发起校验。
4. **前提**:配置 **`playx.verify_token_local_only = false`**,且 **`playx.api.base_url`** 已配置为可访问的 PlayX 基地址。
5. PlayX 返回 **`user_id``username`**(及可选会话过期时间等)。
6. 商城写入 **`mall_playx_session`**`session_id` + 上述 `user_id`/`username` + 过期时间),后续 H5 可用 **`session_id`** 或 **`token`(商城临时 token见模式 B** 调用资产/领取等接口。
幂等与安全:
- H5 **不要**把 PlayX 的 `user_id` 当作唯一可信凭据直传下单;**以 token 换 session** 或由商城签发 token 的流程为准。
- PlayX 侧 Token Verification API 的鉴权/签名若有按双方约定可参考《PlayX-接口文档》§2.1)。
#### 4.1.3 模式 B本地 / 无 PlayX 环境(商城自校验,不请求 PlayX
用于开发、联调前自测、或 PlayX 接口未就绪时:
1. 配置 **`playx.verify_token_local_only = true`**(环境变量 **`PLAYX_VERIFY_TOKEN_LOCAL_ONLY`**,默认可为开启,以项目 `config/playx.php` 为准)。
2. 此时 **`/api/v1/playx/verify-token` 不会访问 PlayX**,仅在商城内校验 **商城临时 token**token 表类型 **`muser`**,由下方 `temLogin` 签发)。
3. 调用 **`GET/POST /api/v1/temLogin?username=...`**(需 **`buildadmin.agent_auth.temp_login_enable = true`**):不存在则创建 **`mall_user`**,并保证存在 **`mall_playx_user_asset`**(含 `playx_user_id`,默认 **`mall_{id}`**),返回 **`userInfo.token`**、**`playx_user_id`**、**`expires_in`** 等。
4. 再用该 token 调用 **`verify-token`** 可得到 **`session_id`**,与模式 A 一样供后续接口使用;或直接带 **`token` / `ba-token`** 调资产等接口见《PlayX-接口文档》§3.1)。
#### 4.1.4 会话续期与前端约定
- **会话续期**:玩家停留时间较长时,若商城 API 返回 token/session 失效(如 401H5 可通过 postMessage 请 PlayX 父页面 **重新派发 PlayX token**(模式 A模式 B 下可重新 **`temLogin`** 或走 **`/api/common/refreshToken`**`muser-refresh`)换取新 access token。
- 具体错误码与 Header`ba-token`以前端与《PlayX-接口文档》为准。
### 4.2 每日 T+1 入池PlayX → 商城)
1. PlayX 在每日固定时间向商城调用 **Daily Push API**,推送昨日玩家数据。(**注:请确认并约定好 `date` 字段对应的具体时区边界,如以 UTC+8 为准**)。
2. 商城按 `user_id + date` 幂等去重入库。由于不支持通过重复推送做数据修正,**若 PlayX 发现个别账单算错了,请联系商城运营在后台进行人工调账处理**,勿重复推送。
3. 商城计算:
- 新增保障金(待领取积分增量)
- 今日可领取上限
4. 会员次日进入商城时,可在首页看到更新后的 LockedPoints 与 TodayLimit。
### 4.3 领取流程Locked → Available
1. 会员在首页点击“领取”。
2. 商城后端校验LockedPoints > 0且 TodayLimit - TodayClaimed > 0。
3. 商城计算 `canClaim = min(LockedPoints, TodayLimit - TodayClaimed)`,并原子更新:
- LockedPoints -= canClaim
- AvailablePoints += canClaim
- TodayClaimed += canClaim
4. 返回最新资产,前端刷新。
幂等:
- 领取操作建议使用 `claim_request_id`(由前端生成或后端生成返回)实现幂等,避免重复点击导致重复领取。
### 4.4 红利兑换(商城 → PlayX 发放)
1. 会员在“红利”商品点击兑换并确认(**为避免客诉,商城前端会提示会员:红利发放预计在此后约 10 分钟内入账,请耐心等待**)。
2. 商城创建 BONUS 订单PENDING并校验/扣减可用积分(原子扣减)。
3. 商城调用 PlayX **Bonus Grant API**,传递红利发放信息(字段见 5.3)。
4. 若 PlayX API 返回初步排队接收成功HTTP 200 且 `status="accepted"`
- 商城订单保持 PENDING等待 PlayX 侧 10 分钟 Cron 最终发放/入账)。
- 记录 `playx_transaction_id`(或外部流水号)用于后续追踪。
- **商城后端将通过调用 PlayX 的 “交易状态查询 API”见 5.5)来轮询获取最终结果**,最终确认为成功后,商城订单才会流转闭环为 COMPLETED。
5. 若 PlayX API 返回失败:
- 订单保持 PENDING并记录失败原因与下一次可重试时间
- 支持后台“手动重试”(见 6.3
- 若经过 N 次重试仍失败或确认 PlayX 侧不可达成:订单转 REJECTED 并退回积分(见 6.2
### 4.5 实物兑换(商城后台人工处理)
1. 会员选择实物并填写收货信息(姓名/电话/地址)。
2. 商城创建 PHYSICAL 订单PENDING并原子扣减可用积分。
3. 后台运营:
- 发货:录入物流公司与单号 → 状态 SHIPPED
- 驳回:录入原因 → 状态 REJECTED → 自动退回积分
### 4.6 提现回平台余额(商城 → PlayX 入账)
1. 会员在“提现到平台余额”商品点击提现并确认(**前端同样需向用户提示约 10 分钟入账预期**)。
2. 商城创建 WITHDRAW 订单PENDING并原子扣减可用积分。
3. 商城调用 PlayX **Balance Credit API**(或同一发放接口的提现模式),传入入账信息。
4. 若 PlayX API 返回初步排队接收成功HTTP 200 且 `status="accepted"`
- 商城订单保持 PENDING等待 PlayX 侧 10 分钟 Cron 最终入账)。
- 记录 `playx_transaction_id`(或外部流水号)用于后续追踪。
- **商城后端通过「交易状态查询 API」见 5.5)轮询获取终态**,确认成功后订单才流转为 COMPLETED。
5. 若 PlayX API 返回失败(非 200 或 `status``accepted`):失败处理同 4.4。
## 5. 接口清单(按调用方向)
> 说明:以下为接口“结构与字段清单”。具体 URL、Header、签名算法、错误码需 PlayX 提供或双方确认后固化。
### 5.1 PlayX → MallDaily Push API每日推送
- **目的**:推送昨日玩家数据,用于 T+1 计算入池与领取上限。
- **幂等键**`user_id + date`date 建议为 PlayX 业务日)
- **Method/Path建议**`POST /api/v1/playx/daily-push`
请求字段说明(最小集合,来自现有资料):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `request_id` | String | 是 | 请求的唯一网关流水号,商城端用于日志追踪和外层防重。 |
| `date` | String | 是 | 数据归属的业务日期(如 `"2026-03-18"`),用于限定该批数据的生效周期。 |
| `user_id` | String | 是 | 玩家在 PlayX 的唯一标识 ID在此商城体系中以此作为核心绑定主键。 |
| `username` | String | 否 | 玩家展示名,仅用于后台日志人工可读性或冗余展示,不作业务主键。 |
| `lifetime_total_deposit` | Decimal | 否 | 玩家历史总充值(如有需要用于玩家 VIP 分层,当前传值保留即可)。 |
| `lifetime_total_withdraw` | Decimal | 否 | 玩家历史总提现(储备字段)。 |
| `yesterday_win_loss_net` | Decimal | 是 | 昨日净输赢金额(如果玩家亏损,应为负数)。**注务必是已扣除返点、红利、奖励、推荐佣金、VIP Bonus 的税后净额**,严格代表玩家的真实净负盈利。 |
| `yesterday_total_deposit` | Decimal | 是 | 昨日玩家总充值金额积分商城专门用此字段来计算“今日可领取上限TodayLimit”。 |
请求示例:
```json
{
"request_id": "px_20260319_000001",
"date": "2026-03-18",
"user_id": "U123",
"username": "demo_user_01",
"lifetime_total_deposit": 5000.0,
"lifetime_total_withdraw": 2000.0,
"yesterday_win_loss_net": -120.5,
"yesterday_total_deposit": 50.0
}
```
响应字段说明(建议):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `request_id` | String | 是 | 完全透传原请求的 `request_id`,便于双向日志匹配追踪。 |
| `accepted` | Boolean | 是 | `true` 标识商城已成功接收并解析了该批数据。 |
| `deduped` | Boolean | 是 | 若 `true`,标识该条数据因 `user_id + date` 已存在而被商城系统幂等静默丢弃(去重)。 |
| `message` | String | 否 | 成功或失败的补充说明(如 `"ok"``"duplicate input"` 等异常提示)。 |
响应示例:
```json
{
"request_id": "px_20260319_000001",
"accepted": true,
"deduped": false,
"message": "ok"
}
```
### 5.2 Mall → PlayXToken Verification API
- **目的**:商城后端校验 token/session获取可信 `user_id``username`
- **Method/Path示例占位**`POST /api/v1/auth/verify-token`
请求字段说明(建议):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `request_id` | String | 是 | 商城系统生成的唯一请求流水号。 |
| `token``session` | String | 是 | 从带有商城的 Iframe `postMessage` 接收到的用户加密登录散列或临时会话凭证。 |
请求示例:
```json
{
"request_id": "mall_20260319_9f1b6d",
"token": "eyJhbGciOi..."
}
```
响应字段说明(建议):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `request_id` | String | 是 | 透传请求时的 `request_id`。 |
| `user_id` | String | 是 | 该凭证解密后对应的、在 PlayX 平台具有唯一性的玩家专属 ID。 |
| `username` | String | 否 | 该玩家显示名用于加载商城的界面头部“欢迎xxx”渲染。 |
| `token_expire_at` | String | 否 | Token 的物理过期时间(如 ISO8601用于商城前端预判是否到了需要执行无感续期重置的底线时间。 |
响应示例:
```json
{
"request_id": "mall_20260319_9f1b6d",
"user_id": "U123",
"username": "demo_user_01",
"token_expire_at": "2026-03-19T10:12:00Z"
}
```
### 5.3 Mall → PlayXBonus Grant API红利发放
来自 PlayX 现有字段清单(待 PlayX 确认最终口径):
请求字段说明(待 PlayX 确认最终口径):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `request_id` | String | 是 | 商城发起的 HTTP 请求流水号(纯用于网关层)。 |
| `externalTransactionId` | String | 是 | **核心防重键:强制要求 PlayX 凭此字段做完全的幂等拦截**。这是商城侧派发红利的唯一本地订单号(如 `"BONUS_ORD001"`)。 |
| `user_id` | String | 是 | 要派发红利的玩家在 PlayX 的基础 ID这需对齐每日推送。 |
| `memberLogin` | String | 否 | 玩家登录名(若当前 PlayX 核心接口必须传登录名,则商城会补充;若以 `user_id` 为准,此项可废弃)。 |
| `amount` | Decimal | 是 | 实际加给玩家游戏余额或红利钱包的具体现金数字。 |
| `rewardName` | String | 否 | 商城中对应的该红利商品名称,用于让用户后续在 PlayX 流水里看懂这笔钱从何而来。 |
| `description` | String | 否 | 系统行为备注说明(如 `"PointsMall bonus"`)。 |
| `memberInboxMessage` | String | 否 | 是否需借调此时机向玩家发送站内站群信内容提示。 |
| `category` | String | 是 | 标明该红利在游戏侧的所属业务类别的枚举代码(如 `daily`)。 |
| `categoryTitle` | String | 否 | 该红利业务类别的中文展示名称。 |
| `multiplier` (或 `turnover`) | Int | 是 | 款项入账后,玩家需完成的打码流水约束倍数(如 1 倍或 5 倍)。 |
| `startTime` / `endTime` | String | 否 | 红利生效时间窗口(起止时间,视 PlayX 规则传参)。 |
请求示例:
```json
{
"request_id": "mall_bonus_20260319_000001",
"externalTransactionId": "BONUS_ORD20260319_000001",
"user_id": "U123",
"memberLogin": "demo_user_01",
"amount": 50.0,
"rewardName": "每日回馈 50",
"description": "PointsMall bonus redemption",
"memberInboxMessage": "红利已提交,预计 10 分钟内到账",
"category": "daily",
"categoryTitle": "每日回馈",
"multiplier": 1,
"startTime": "2026-03-19T00:00:00Z",
"endTime": "2026-03-19T23:59:59Z"
}
```
响应字段说明(建议):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `request_id` | String | 是 | 透传请求号。 |
| `playx_transaction_id` | String | 否 | PlayX 内部初创的接收入列单号或派发流水号,商城会将其归档以备应对极端客诉争议寻找记录用。 |
| `status` | String | 是 | 核心状态枚举。若为 `accepted`,表示请求成功列入 10 分钟 Cron商城中止重试其他值皆触发商城的补偿拦截网。 |
| `message` | String | 否 | 对入列状态的额外提示信息内容。 |
响应示例:
```json
{
"request_id": "mall_bonus_20260319_000001",
"playx_transaction_id": "PX_TX_778899",
"status": "accepted",
"message": "queued"
}
```
### 5.4 Mall → PlayXBalance Credit API提现回平台余额
字段建议与 5.3 保持结构一致,至少包含:
请求字段说明(建议与 5.3 保持结构一致):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `request_id` | String | 是 | 商城下发的网络请求追溯号。 |
| `externalTransactionId` | String | 是 | **提现唯一单号,提现接口也需要基于此值做绝对拦截幂等功能**。 |
| `user_id` | String | 是 | 申请提现的玩家 ID。 |
| `memberLogin` | String | 否 | 玩家名(视老接口历史包袱兼容)。 |
| `amount` | Decimal | 是 | 本次提现要充入 PlayX 主游戏平台真金余额池的具体现金。 |
| `multiplier` (或 `turnover_rule`) | Int | 是 | 本真金提现入账后的硬性流水锁定要求倍数限制。 |
| `description` | String | 否 | 日志源记录说明(如 `"PointsMall withdraw"`)。 |
请求示例:
```json
{
"request_id": "mall_withdraw_20260319_000001",
"externalTransactionId": "WITHDRAW_ORD20260319_000002",
"user_id": "U123",
"memberLogin": "demo_user_01",
"amount": 100.0,
"multiplier": 1,
"description": "PointsMall withdraw to PlayX balance"
}
```
响应说明与 5.3 (Bonus Grant API) 保持一致,主要接收 `status="accepted"` 作为暂挂确认。
```json
{
"request_id": "mall_withdraw_20260319_000001",
"playx_transaction_id": "PX_TX_889900",
"status": "accepted",
"message": "queued"
}
```
### 5.5 Mall → PlayXTransaction Status Query API交易终态查询
- **目的**:红利/提现申请经 PlayX 接收后(即返回 `accepted` 后)可能处于排队发放下款状态(如 10 分钟 Cron。商城将通过此接口查询最终业务结果用于闭环商城自身的 PENDING 订单。
- **Method/Path预留**`GET /api/v1/transaction/status`
- **传参方式**:使用 **Query String** 传递查询主键(若 PlayX 更倾向 POST可改为 `POST` + JSON body但需在联调前双方定稿一种即可
示例:
- `GET /api/v1/transaction/status?externalTransactionId=WITHDRAW_ORD20260319_000002`
- `GET /api/v1/transaction/status?playx_transaction_id=PX_TX_889900`(与 `externalTransactionId` 二选一,推荐优先 `externalTransactionId`
**轮询建议(商城侧)**:首次调用可在入队成功后约 1 分钟开始;之后间隔约 **60 秒** 查询一次,直至 `status``COMPLETED``FAILED`,或累计轮询达到约 **1520 分钟**(与 10 分钟 Cron 留足余量)仍未终态则告警并转人工跟进。
请求字段说明(建议选其一作主键):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `externalTransactionId` | String | 是* | 商城之前提报请求时创建挂钩的原始提现单号,推荐此查维优先。 |
| `playx_transaction_id` | String | 否 | 如果之前排队响应抛出了内部派发流水,也可以持此作为二级兜底查询条件。(二选一必填) |
响应字段说明(建议):
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `status` | String | 是 | 该笔资产调拨定时任务执行的彻底终态。只有两种预期终点:**`COMPLETED`**(入账成功) 或 **`FAILED`**(发放彻底阻断:如平台风控/未通过规则/账号封禁)。如果返回 `PENDING` 表示该 10 分钟 Cron 仍然没碰这笔单。 |
| `amount` | Decimal | 否 | 最后实际结算派发的精准明细金额数。 |
| `message` | String | 否 | 若拦截至 `FAILED` 终态,该字段负责说明 PlayX 端驳回的业务层原因,便于商城后端登记审计并自动回退积分。 |
## 6. 一致性、幂等与退回规则
### 6.1 幂等原则
- **每日推送**:以 `user_id + date` 去重,重复推送不得导致重复入池。
- **兑换/提现交易**:以 `externalTransactionId` 幂等(商城生成并传给 PlayX
- **领取**:以 `claim_request_id` 幂等,避免重复领取。
### 6.2 退积分规则(建议统一)
- **红利/提现**
- PlayX API 调用失败:订单保持 PENDING进入可重试队列不立即退积分避免“退了但 PlayX 已受理/最终入账”的不一致)。
- 当订单被判定为“最终失败”(例如超过最大重试次数或 PlayX 返回不可恢复错误)时:订单转 REJECTED退回积分并记录原因。
- **实物**
- 驳回必须退回积分,并记录 `reject_reason`
### 6.3 重试与后台操作边界
- 仅允许对 “尚未收到 `status = \"accepted\"` 响应、且可以确认未成功发放/未入账” 的订单发起重试。
- 每次重试必须生成并记录 `retry_request_id` 与操作者审计日志。
- **强制防重约定****PlayX 必须根据 `externalTransactionId` 提供严格的幂等拦截能力!**由于网络请求存在“Read Timeout读超时”的黑盒场景即 PlayX 实际已处理但响应由于网络中断未抵达商城,商城将会发起重试保护。如果 PlayX 不去拦截此重发单号,将必然出现给用户发双份钱的高危资损事故。
接收成功与终态闭环判定(关键约定):
- **第一步(接收排队)**:本系统调用发放类 API仅当收到 HTTP 200 且 `status = "accepted"` 时,视为 PlayX 已接收入队成功,此时商城**绝不再对发放接口发起新的成功路径请求**。若返回非 200、或响应超时、或未能解析出明确 `accepted`:商城可对**同一** `externalTransactionId` 进行有限次重试;**PlayX 须对该单号严格幂等**——重复请求不得产生第二笔发放,且应返回与首次受理一致或可识别的幂等结果(如再次返回 `accepted` 或明确 `DUPLICATE_REQUEST` 等,由双方约定响应形态)。
- **第二步(确认终态闭环)**:针对已入队返回 accepted 的订单,商城将调用**“交易终态查询 API”5.5**验证 PlayX 后台的最终发放结果实现闭环更新。
默认重试策略(建议):
- **自动重试**:对 `PLAYX_UPSTREAM_ERROR`/超时类错误,最多 3 次(间隔 1min/5min/15min
- **不重试**`INVALID_SIGNATURE``REQUEST_EXPIRED``RULE_NOT_SATISFIED``INVALID_TOKEN`(需要修复请求或重新鉴权)。
- **人工重试**:后台按钮触发,要求输入原因并记录审计。
## 7. 安全要求Shared Secret Key
建议所有 PlayX ↔ Mall 的后端调用统一:
- Header
- `X-Request-Id`
- `X-Timestamp`
- `X-Signature`
- 签名:使用共享 `SecretKey`,对 request body + timestamp + requestId 进行 HMAC具体算法由双方定稿
- 时效timestamp 允许偏差窗口(例如 5 分钟),超出拒绝。
签名建议(可直接落地的默认):
- `X-Signature = HMAC_SHA256(secret, canonical_string)`
- `canonical_string = X-Timestamp + \"\\n\" + X-Request-Id + \"\\n\" + HTTP_METHOD + \"\\n\" + PATH + \"\\n\" + SHA256(REQUEST_BODY_JSON)`
其中:
- `PATH` 不含域名与 querystring例如 `/api/v1/playx/daily-push`)。
- `REQUEST_BODY_JSON` 使用原始 request body不做 key 排序时需双方约定序列化方式更推荐双方统一为“key 排序后的紧凑 JSON”
## 8. 错误码与可观测(建议)
最低要求:
- `INVALID_SIGNATURE`
- `REQUEST_EXPIRED`
- `INVALID_TOKEN`
- `DUPLICATE_REQUEST`
- `INSUFFICIENT_POINTS`
- `RULE_NOT_SATISFIED`
- `PLAYX_UPSTREAM_ERROR`
错误码返回结构(建议统一):
```json
{
"request_id": "xxx",
"code": "PLAYX_UPSTREAM_ERROR",
"message": "timeout",
"retryable": true
}
```
### 8.1 幂等:同一 `externalTransactionId` 重复调用Bonus Grant / Balance Credit
PlayX 须保证:**同一** `externalTransactionId` 无论被调用多少次,**资金侧最多只入账一次**。商城在「读超时重试」或联调压测时会重复提交同一单号,响应须符合以下 **两种约定之一**(联调前择一写死,避免双方解析不一致)。
**模式 A推荐再次请求仍返回 HTTP 200且与首次受理语义一致**
- 第二次及以后请求:`status` 仍为 `"accepted"`(或文档约定的等价成功态),**不得**再次触发新的发放队列条目导致双发。
- 建议同时带回**首次**的 `playx_transaction_id`(若与首次不同,须在联调中禁止或说明兼容规则)。
```json
{
"request_id": "mall_bonus_20260319_000099",
"playx_transaction_id": "PX_TX_778899",
"status": "accepted",
"message": "duplicate externalTransactionId, already accepted"
}
```
**模式 B显式重复错误码HTTP 状态可与 PlayX 规范一致,如 200 或 409联调前约定**
- `code``DUPLICATE_REQUEST`(或双方统一的幂等冲突码),`retryable``false`,提示商城勿再重试发放接口、改查 5.5 终态。
```json
{
"request_id": "mall_bonus_20260319_000099",
"code": "DUPLICATE_REQUEST",
"message": "externalTransactionId already processed",
"retryable": false,
"playx_transaction_id": "PX_TX_778899"
}
```
日志与审计:
- 每次跨系统调用必须落 `request_id`、入参摘要、响应摘要、耗时、结果码。
## 9. 联调与验收清单
### 9.1 鉴权
- token 正常/过期/无效/重复请求
- postMessage 未收到 token 的超时提示
### 9.2 每日推送
- 正常推送 1 次
- 同一 `user_id+date` 重复推送(应 dedup
- 跨时区日期边界(按约定业务日)
### 9.3 领取
- locked=0 不可领取
- 上限不足部分领取
- 幂等:重复点击不重复加积分
### 9.4 红利/提现
- 发放接口HTTP 200 且 `status="accepted"` 后,订单 PENDING记录 `playx_transaction_id`**不再对发放接口重放**(终态靠 5.5)。
- 发放接口:非 200 / 超时 / 非 `accepted`:失败原因落库,可自动或人工重试;**PlayX 对同一 `externalTransactionId` 须严格幂等**。
- **交易终态查询5.5**:按 `externalTransactionId` 查询,验证返回 `COMPLETED` / `FAILED` / `PENDING`;长时间 `PENDING` 走告警与人工。
- 幂等联调:同一 `externalTransactionId` 连续发送 2 次PlayX 侧**不得重复入账**,第二次响应须符合双方约定的幂等语义。
### 9.5 实物
- 提交收货信息
- 发货录入物流单号
- 驳回退积分并展示原因
## 10. 需要 PlayX 提供/确认的信息清单(用于联调收口)
- **Token Verification API**URL、请求/响应字段、错误码、token 有效期/刷新策略、是否支持 session。
- **Daily Push API**推送时间点、时区、date 口径(业务日还是自然日)、失败重发策略、字段定义(特别是 `yesterday_win_loss_net` 的扣项范围)。
- **Bonus Grant API / Balance Credit API**URL、鉴权签名要求、字段最终表、**确认以 `externalTransactionId` 作为拦截幂等键**,以及返回的 `playx_transaction_id` 定义与查询方式。
- **交易终态查询 API如适用**:提供专门供商城拉取订单最终入账结果的查询接口 URL 及返回结构。
- **发送站内信 API如适用**:在管理后台手动处理实物商品发货/驳回时,如需通过信箱通知用户,请提供外部触发站内信的 API 渠道。
- **枚举值配置**:请尽早提供发放接口中 `category` 等字段的固定枚举值字典,以便商城后台完成商品发货配置项的落库。