docs文档
This commit is contained in:
508
docs/PlayX-对接文档(积分商城).md
Normal file
508
docs/PlayX-对接文档(积分商城).md
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
## 0. 交付说明(给 PlayX)
|
||||||
|
|
||||||
|
- **交付物**:本文件(接口清单 + 业务流程 + 联调验收清单)。
|
||||||
|
- **建议联调顺序**:Token 验证 → 每日推送 → 领取 → 红利发放 → 提现入账 → 实物后台处理。
|
||||||
|
- **约定**:接口 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
1. 用户在 PlayX 内打开积分商城入口(iframe)。
|
||||||
|
2. PlayX 前端通过 postMessage 发送 `token/session` 给商城前端。
|
||||||
|
3. 商城后端调用 PlayX 的 **Token Verification API** 校验 token。
|
||||||
|
4. PlayX 返回 `user_id`、`username`(以及会话有效期等)。
|
||||||
|
5. 商城建立会话,返回会员资产与商品列表数据。
|
||||||
|
|
||||||
|
幂等、安全与会话续期:
|
||||||
|
|
||||||
|
- 前端不信任 `user_id` 直传;只接收 token/session。
|
||||||
|
- Token 验证接口需要签名/鉴权(见第 7 节)。
|
||||||
|
- **会话续期**:由于玩家访问积分商城可能停留时间较长,当商城调用任意 API 遇到 Token 校验过期(如 HTTP 401)时,商城前端会通过 postMessage 向 PlayX 父级页面请求派发新的 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 → Mall:Daily 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 → PlayX:Token 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 → PlayX:Bonus 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 → PlayX:Balance 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 → PlayX:Transaction 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`,或累计轮询达到约 **15~20 分钟**(与 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` 等字段的固定枚举值字典,以便商城后台完成商品发货配置项的落库。
|
||||||
|
|
||||||
624
docs/PlayX-接口文档.md
Normal file
624
docs/PlayX-接口文档.md
Normal file
@@ -0,0 +1,624 @@
|
|||||||
|
# PlayX 接口文档(按调用方向拆分)
|
||||||
|
|
||||||
|
说明:本文档严格依据当前代码 `app/api/controller/v1/Playx.php` 与定时任务 `app/process/PlayxJobs.php` 整理。
|
||||||
|
|
||||||
|
三类接口分别为:
|
||||||
|
- `积分商城 -> PlayX`(PlayX 调用商城)
|
||||||
|
- `PlayX -> 积分商城`(商城调用 PlayX)
|
||||||
|
- `积分商城 -> H5`(H5 调用商城)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 积分商城 -> PlayX(PlayX 调用商城)
|
||||||
|
|
||||||
|
### 1.1 Daily Push API
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/daily-push`
|
||||||
|
|
||||||
|
#### Header(签名校验:可选)
|
||||||
|
当 `playx.daily_push_secret` 配置非空时,需要携带:
|
||||||
|
- `X-Request-Id`:请求 ID
|
||||||
|
- `X-Timestamp`:时间戳
|
||||||
|
- `X-Signature`:签名(HMAC_SHA256)
|
||||||
|
|
||||||
|
服务端签名计算:
|
||||||
|
- `canonical = X-Timestamp + "\n" + X-Request-Id + "\nPOST\n/api/v1/playx/daily-push\n" + sha256(json_body)`
|
||||||
|
- `expected = hash_hmac('sha256', canonical, daily_push_secret)`
|
||||||
|
- 校验:`hash_equals(expected, X-Signature)`
|
||||||
|
|
||||||
|
#### Body
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 外部推送请求号(原样返回) |
|
||||||
|
| `date` | string(YYYY-MM-DD) | 是 | 业务日期(入库到 `mall_playx_daily_push.date`) |
|
||||||
|
| `user_id` | string | 是 | PlayX 用户 ID(用于幂等) |
|
||||||
|
| `username` | string | 否 | 展示冗余 |
|
||||||
|
| `yesterday_win_loss_net` | number | 否 | 昨日净输赢(仅当 `< 0` 时计算新增保障金) |
|
||||||
|
| `yesterday_total_deposit` | number | 否 | 昨日总充值(用于计算今日可领取上限) |
|
||||||
|
| `lifetime_total_deposit` | number | 否 | 历史总充值 |
|
||||||
|
| `lifetime_total_withdraw` | number | 否 | 历史总提现 |
|
||||||
|
|
||||||
|
#### 幂等规则
|
||||||
|
* 幂等键:`user_id + date`
|
||||||
|
* 重复推送:不会重复入账,返回 `data.deduped=true`
|
||||||
|
|
||||||
|
#### 返回(Response)
|
||||||
|
外层通用返回结构:`{ code, msg, time, data }`
|
||||||
|
|
||||||
|
成功(首次入库):
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.request_id` | string | 原样返回 |
|
||||||
|
| `data.accepted` | boolean | `true` |
|
||||||
|
| `data.deduped` | boolean | `false` |
|
||||||
|
| `data.message` | string | `ok` |
|
||||||
|
|
||||||
|
成功(重复推送):
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.request_id` | string | 原样返回 |
|
||||||
|
| `data.accepted` | boolean | `true` |
|
||||||
|
| `data.deduped` | boolean | `true` |
|
||||||
|
| `data.message` | string | `duplicate input` |
|
||||||
|
|
||||||
|
失败:
|
||||||
|
* 当缺少必填字段:code=0,msg 为缺少字段错误
|
||||||
|
* 当签名不正确:HTTP 401,code=0,msg 为 `INVALID_SIGNATURE`
|
||||||
|
|
||||||
|
#### 示例(未开启签名校验)
|
||||||
|
请求:
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/daily-push' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{
|
||||||
|
"request_id":"req_1001",
|
||||||
|
"date":"2026-03-18",
|
||||||
|
"user_id":"U123",
|
||||||
|
"username":"demo_user",
|
||||||
|
"yesterday_win_loss_net":-120.5,
|
||||||
|
"yesterday_total_deposit":50,
|
||||||
|
"lifetime_total_deposit":5000,
|
||||||
|
"lifetime_total_withdraw":2000
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(首次):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"request_id": "req_1001",
|
||||||
|
"accepted": true,
|
||||||
|
"deduped": false,
|
||||||
|
"message": "ok"
|
||||||
|
},
|
||||||
|
"time": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. PlayX -> 积分商城(商城调用 PlayX)
|
||||||
|
|
||||||
|
> 下面这些接口由 PlayX 提供。商城侧仅按“请求参数 + 期望返回判定条件”发起调用与处理结果。
|
||||||
|
|
||||||
|
### 2.1 Token Verification API
|
||||||
|
* 方法:`POST`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.token_verify_url}`
|
||||||
|
* 默认:`/api/v1/auth/verify-token`
|
||||||
|
|
||||||
|
#### 请求 Body(商城侧发送)
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 形如 `mall_{uniqid}` |
|
||||||
|
| `token` | string | 是 | 前端传入的 PlayX token |
|
||||||
|
|
||||||
|
#### 返回(期望)
|
||||||
|
商城侧校验:
|
||||||
|
* HTTP 状态码必须为 `200`
|
||||||
|
* 且响应体中必须包含 `user_id`
|
||||||
|
|
||||||
|
期望字段(示例):
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `user_id` | string | 必选 |
|
||||||
|
| `username` | string | 可选 |
|
||||||
|
| `token_expire_at` | string | 可选(能被 `strtotime` 解析) |
|
||||||
|
|
||||||
|
示例(成功):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id": "U123",
|
||||||
|
"username": "demo_user",
|
||||||
|
"token_expire_at": "2026-04-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例(失败):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "invalid token"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 Bonus Grant API
|
||||||
|
* 方法:`POST`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.bonus_grant_url}`
|
||||||
|
* 默认:`/api/v1/bonus/grant`
|
||||||
|
|
||||||
|
#### 请求 Body(商城侧发送)
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 形如 `mall_bonus_{uniqid}` |
|
||||||
|
| `externalTransactionId` | string | 是 | `MallPlayxOrder.external_transaction_id` |
|
||||||
|
| `user_id` | string | 是 | PlayX 用户 ID |
|
||||||
|
| `amount` | number | 是 | `MallPlayxOrder.amount` |
|
||||||
|
| `rewardName` | string | 是 | `mall_item.title` |
|
||||||
|
| `category` | string | 是 | `mall_item.category`(默认 `daily`) |
|
||||||
|
| `categoryTitle` | string | 是 | `mall_item.category_title` |
|
||||||
|
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
|
||||||
|
|
||||||
|
#### 返回(期望)
|
||||||
|
商城侧判定:
|
||||||
|
* HTTP 状态码 `200`
|
||||||
|
* 且 `data.status === "accepted"`
|
||||||
|
|
||||||
|
成功时读取:
|
||||||
|
* `data.playx_transaction_id`
|
||||||
|
|
||||||
|
失败时读取:
|
||||||
|
* `data.message` 写入订单 `fail_reason`
|
||||||
|
|
||||||
|
示例(accepted):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "accepted",
|
||||||
|
"playx_transaction_id": "PX_TX_001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例(rejected):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "rejected",
|
||||||
|
"message": "insufficient balance"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 Balance Credit API
|
||||||
|
* 方法:`POST`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.balance_credit_url}`
|
||||||
|
* 默认:`/api/v1/balance/credit`
|
||||||
|
|
||||||
|
#### 请求 Body(商城侧发送)
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 形如 `mall_withdraw_{uniqid}` |
|
||||||
|
| `externalTransactionId` | string | 是 | `MallPlayxOrder.external_transaction_id` |
|
||||||
|
| `user_id` | string | 是 | PlayX 用户 ID |
|
||||||
|
| `amount` | number | 是 | `MallPlayxOrder.amount` |
|
||||||
|
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
|
||||||
|
|
||||||
|
#### 返回(期望)
|
||||||
|
与 Bonus Grant 一致:
|
||||||
|
* `data.status === "accepted"` -> 读取 `playx_transaction_id`
|
||||||
|
* 否则 -> 读取 `message` 写入 `fail_reason`
|
||||||
|
|
||||||
|
示例(accepted):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "accepted",
|
||||||
|
"playx_transaction_id": "PX_TX_002"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例(rejected):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "rejected",
|
||||||
|
"message": "insufficient balance"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 Transaction Status Query API(交易终态查询)
|
||||||
|
* 方法:`GET`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.transaction_status_url}`
|
||||||
|
* 默认:`/api/v1/transaction/status`
|
||||||
|
|
||||||
|
#### Query 参数
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `externalTransactionId` | string | 是 | 订单幂等键 `external_transaction_id` |
|
||||||
|
|
||||||
|
#### 返回(期望)
|
||||||
|
定时任务读取 `data.status`:
|
||||||
|
* `COMPLETED`:商城将订单 `status` 更新为 `COMPLETED`
|
||||||
|
* `FAILED` 或 `REJECTED`:商城将订单 `status=REJECTED`、`grant_status=FAILED_FINAL`,并退回积分
|
||||||
|
* 失败信息取 `data.message` 写入订单 `fail_reason`
|
||||||
|
|
||||||
|
示例(completed):
|
||||||
|
```json
|
||||||
|
{ "status": "COMPLETED" }
|
||||||
|
```
|
||||||
|
|
||||||
|
示例(failed):
|
||||||
|
```json
|
||||||
|
{ "status": "FAILED", "message": "grant rejected by PlayX" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 积分商城 -> H5(服务端提供给 H5 的接口)
|
||||||
|
|
||||||
|
说明:鉴权与用户解析规则由 `resolveUserIdFromRequest()` 决定。
|
||||||
|
* 优先使用 `session_id`(在 `mall_playx_session` 查到且未过期)
|
||||||
|
* 其次使用 `user_id`
|
||||||
|
|
||||||
|
公共鉴权字段:
|
||||||
|
* `session_id`:字符串
|
||||||
|
* `user_id`:字符串
|
||||||
|
|
||||||
|
> 注意:请求参数的取值方式是 `post()` 优先,`get()` 兼容(即同字段既可传 post 也可传 get)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.1 Token 验证
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/verify-token`
|
||||||
|
|
||||||
|
#### 请求 Body
|
||||||
|
必填其一:
|
||||||
|
* `token`(优先读取)
|
||||||
|
* `session`(兼容字段,当 `token` 为空时会被当作 token)
|
||||||
|
|
||||||
|
#### 返回(成功 data)
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `session_id` | string | 写入 `mall_playx_session` |
|
||||||
|
| `user_id` | string | PlayX 用户 ID |
|
||||||
|
| `username` | string | 用户名 |
|
||||||
|
| `token_expire_at` | string | ISO 字符串(服务端 `date('c', expireAt)`) |
|
||||||
|
|
||||||
|
失败:
|
||||||
|
* token 为空:HTTP 401,msg=`INVALID_TOKEN`
|
||||||
|
* PlayX 未配置:msg=`PlayX API not configured`
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
请求:
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/verify-token' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'token=PLAYX_TOKEN_XXX'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(成功示例):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"session_id": "7b1c....",
|
||||||
|
"user_id": "U123",
|
||||||
|
"username": "demo_user",
|
||||||
|
"token_expire_at": "2026-04-01T12:00:00+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 用户资产
|
||||||
|
* 方法:`GET`
|
||||||
|
* 路径:`/api/v1/playx/assets`
|
||||||
|
|
||||||
|
#### 请求参数(鉴权)
|
||||||
|
* `session_id`(优先)
|
||||||
|
* `user_id`(兼容)
|
||||||
|
|
||||||
|
#### 返回(成功 data)
|
||||||
|
若未找到资产:返回 0。
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `locked_points` | int | 待领取积分 |
|
||||||
|
| `available_points` | int | 可用积分 |
|
||||||
|
| `today_limit` | int | 今日可领取上限 |
|
||||||
|
| `today_claimed` | int | 今日已领取 |
|
||||||
|
| `withdrawable_cash` | number(2) | `available_points * points_to_cash_ratio`(保留 2 位) |
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```bash
|
||||||
|
curl -G 'http://localhost:1818/api/v1/playx/assets' --data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(示例):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"time": 1712345678,
|
||||||
|
"data": {
|
||||||
|
"locked_points": 100,
|
||||||
|
"available_points": 50,
|
||||||
|
"today_limit": 200,
|
||||||
|
"today_claimed": 80,
|
||||||
|
"withdrawable_cash": 5.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 领取(Claim)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/claim`
|
||||||
|
|
||||||
|
#### 请求 Body
|
||||||
|
必填:
|
||||||
|
* `claim_request_id`:幂等键(string,唯一)
|
||||||
|
鉴权:
|
||||||
|
* `session_id` 或 `user_id`
|
||||||
|
|
||||||
|
#### 返回(成功 data)
|
||||||
|
与 `用户资产` 返回字段一致(资产快照)。
|
||||||
|
|
||||||
|
幂等:
|
||||||
|
* `claim_request_id` 已存在:不会重复入账,直接返回当前资产快照
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/claim' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'claim_request_id=claim_001' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(首次领取,示例):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Claim success",
|
||||||
|
"time": 1712345679,
|
||||||
|
"data": {
|
||||||
|
"locked_points": 60,
|
||||||
|
"available_points": 90,
|
||||||
|
"today_limit": 200,
|
||||||
|
"today_claimed": 120,
|
||||||
|
"withdrawable_cash": 9.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(幂等重复,示例:可能 msg 为空):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"time": 1712345680,
|
||||||
|
"data": {
|
||||||
|
"locked_points": 60,
|
||||||
|
"available_points": 90,
|
||||||
|
"today_limit": 200,
|
||||||
|
"today_claimed": 120,
|
||||||
|
"withdrawable_cash": 9.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 商品列表
|
||||||
|
* 方法:`GET`
|
||||||
|
* 路径:`/api/v1/playx/items`
|
||||||
|
|
||||||
|
#### 请求参数
|
||||||
|
* `type`(可选):`BONUS` | `PHYSICAL` | `WITHDRAW`
|
||||||
|
|
||||||
|
不传或空:返回 `mall_item.status=1` 且不过滤 type 的商品列表。
|
||||||
|
|
||||||
|
#### 返回(成功 data)
|
||||||
|
* `list`:商品列表(直接返回 `MallItem` 的字段数组;包含扩展字段:`amount/multiplier/category/category_title` 等)
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
请求:
|
||||||
|
```bash
|
||||||
|
curl -G 'http://localhost:1818/api/v1/playx/items' --data-urlencode 'type=WITHDRAW'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(示例):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"time": 1712345685,
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 321,
|
||||||
|
"title": "提现档位A",
|
||||||
|
"type": 3,
|
||||||
|
"score": 1000,
|
||||||
|
"amount": 100.0,
|
||||||
|
"multiplier": 1,
|
||||||
|
"category": "withdraw",
|
||||||
|
"category_title": "提现"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 红利兑换(Bonus Redeem)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/bonus/redeem`
|
||||||
|
|
||||||
|
#### 请求 Body
|
||||||
|
必填:
|
||||||
|
* `item_id`:商品 ID(要求 `mall_item.type=BONUS` 且 `status=1`)
|
||||||
|
鉴权:
|
||||||
|
* `session_id` 或 `user_id`
|
||||||
|
|
||||||
|
#### 返回(成功)
|
||||||
|
* `msg`:`Redeem submitted, please wait about 10 minutes`
|
||||||
|
* `data.order_id`:订单 ID
|
||||||
|
* `data.status`:`PENDING`
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/bonus/redeem' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'item_id=123' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(示例):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Redeem submitted, please wait about 10 minutes",
|
||||||
|
"time": 1712345686,
|
||||||
|
"data": {
|
||||||
|
"order_id": 456,
|
||||||
|
"status": "PENDING"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 实物兑换(Physical Redeem)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/physical/redeem`
|
||||||
|
|
||||||
|
#### 请求 Body
|
||||||
|
必填:
|
||||||
|
* `item_id`:商品 ID(要求 `mall_item.type=PHYSICAL` 且 `status=1`)
|
||||||
|
* `receiver_name`:收货人
|
||||||
|
* `receiver_phone`:收货电话
|
||||||
|
* `receiver_address`:收货地址
|
||||||
|
鉴权:
|
||||||
|
* `session_id` 或 `user_id`
|
||||||
|
|
||||||
|
#### 返回(成功)
|
||||||
|
* `msg`:`Redeem success`
|
||||||
|
* `data`:`null`
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/physical/redeem' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'item_id=200' \
|
||||||
|
--data-urlencode 'receiver_name=张三' \
|
||||||
|
--data-urlencode 'receiver_phone=18800001111' \
|
||||||
|
--data-urlencode 'receiver_address=北京市朝阳区XX路XX号' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(示例):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Redeem success",
|
||||||
|
"time": 1712345687,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.7 提现申请(Withdraw Apply)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/withdraw/apply`
|
||||||
|
|
||||||
|
#### 请求 Body
|
||||||
|
必填:
|
||||||
|
* `item_id`:商品 ID(要求 `mall_item.type=WITHDRAW` 且 `status=1`)
|
||||||
|
鉴权:
|
||||||
|
* `session_id` 或 `user_id`
|
||||||
|
|
||||||
|
#### 返回(成功)
|
||||||
|
* `msg`:`Withdraw submitted, please wait about 10 minutes`
|
||||||
|
* `data.order_id`:订单 ID
|
||||||
|
* `data.status`:`PENDING`
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/withdraw/apply' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'item_id=321' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(示例):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Withdraw submitted, please wait about 10 minutes",
|
||||||
|
"time": 1712345688,
|
||||||
|
"data": {
|
||||||
|
"order_id": 789,
|
||||||
|
"status": "PENDING"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.8 订单列表
|
||||||
|
* 方法:`GET`
|
||||||
|
* 路径:`/api/v1/playx/orders`
|
||||||
|
|
||||||
|
#### 请求参数
|
||||||
|
* `session_id` 或 `user_id`
|
||||||
|
|
||||||
|
#### 返回(成功 data)
|
||||||
|
* `list`:订单列表(最多 100 条),并包含关联的 `mallItem`(关系对象)
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
请求:
|
||||||
|
```bash
|
||||||
|
curl -G 'http://localhost:1818/api/v1/playx/orders' --data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(示例,简化):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"time": 1712345689,
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 456,
|
||||||
|
"user_id": "U123",
|
||||||
|
"type": "BONUS",
|
||||||
|
"status": "PENDING",
|
||||||
|
"mall_item_id": 123,
|
||||||
|
"points_cost": 100,
|
||||||
|
"amount": 10.0,
|
||||||
|
"external_transaction_id": "BONUS_ORD2026....",
|
||||||
|
"grant_status": "NOT_SENT",
|
||||||
|
"mallItem": {
|
||||||
|
"id": 123,
|
||||||
|
"title": "每日红利",
|
||||||
|
"type": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.9 同步额度(可选)
|
||||||
|
当前代码未实现并未注册路由:`/api/v1/playx/sync-limit`。
|
||||||
|
|
||||||
686
docs/积分商城-PlayX对接实施方案.md
Normal file
686
docs/积分商城-PlayX对接实施方案.md
Normal file
@@ -0,0 +1,686 @@
|
|||||||
|
# 积分商城 PlayX 对接实施方案
|
||||||
|
|
||||||
|
> 基于《积分商城-内部对接与流程说明.md》和《PlayX-对接文档(积分商城).md》整理,结合当前项目结构给出具体落地方案。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、接口创建
|
||||||
|
|
||||||
|
### 1.1 商城需对外提供的接口(PlayX 调用商城)
|
||||||
|
#### Daily Push API
|
||||||
|
接收 PlayX 每日 T+1 数据推送。
|
||||||
|
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/daily-push`
|
||||||
|
|
||||||
|
##### 请求(Header)
|
||||||
|
当配置了 `playx.daily_push_secret`(Daily Push 签名校验)时,需要携带:
|
||||||
|
* `X-Request-Id`:请求 ID
|
||||||
|
* `X-Timestamp`:时间戳
|
||||||
|
* `X-Signature`:签名(HMAC_SHA256)
|
||||||
|
|
||||||
|
签名计算逻辑(服务端):
|
||||||
|
* canonical:`{X-Timestamp}\n{X-Request-Id}\nPOST\n/api/v1/playx/daily-push\n{sha256(json_body)}`
|
||||||
|
* expected:`hash_hmac('sha256', canonical, daily_push_secret)`
|
||||||
|
|
||||||
|
##### 请求(Body)
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 外部推送请求号(原样返回) |
|
||||||
|
| `date` | string(YYYY-MM-DD) | 是 | 业务日期(入库到 `mall_playx_daily_push.date`) |
|
||||||
|
| `user_id` | string | 是 | PlayX 用户 ID(幂等键组成部分) |
|
||||||
|
| `username` | string | 否 | 展示冗余 |
|
||||||
|
| `yesterday_win_loss_net` | number | 否 | 昨日净输赢(仅当 `< 0` 时计算新增保障金) |
|
||||||
|
| `yesterday_total_deposit` | number | 否 | 昨日总充值(用于计算今日可领取上限) |
|
||||||
|
| `lifetime_total_deposit` | number | 否 | 历史总充值 |
|
||||||
|
| `lifetime_total_withdraw` | number | 否 | 历史总提现 |
|
||||||
|
|
||||||
|
##### 幂等
|
||||||
|
* 幂等键:`user_id + date`
|
||||||
|
* 重复推送时不会重复入账,返回 `data.deduped = true`
|
||||||
|
|
||||||
|
##### 返回(Response)
|
||||||
|
通用返回包结构:`{ code, msg, time, data }`
|
||||||
|
|
||||||
|
成功返回(data):
|
||||||
|
* `data.request_id`:原样返回
|
||||||
|
* `data.accepted`:`true`
|
||||||
|
* `data.deduped`:是否幂等命中(`false`=首次入库,`true`=重复推送)
|
||||||
|
* `data.message`:首次为 `ok`,重复为 `duplicate input`
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
无签名校验(`PLAYX_DAILY_PUSH_SECRET` 为空):
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/daily-push' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{
|
||||||
|
"request_id":"req_1001",
|
||||||
|
"date":"2026-03-18",
|
||||||
|
"user_id":"U123",
|
||||||
|
"username":"demo_user",
|
||||||
|
"yesterday_win_loss_net":-120.5,
|
||||||
|
"yesterday_total_deposit":50,
|
||||||
|
"lifetime_total_deposit":5000,
|
||||||
|
"lifetime_total_withdraw":2000
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
成功响应(首次):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"request_id": "req_1001",
|
||||||
|
"accepted": true,
|
||||||
|
"deduped": false,
|
||||||
|
"message": "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.2 商城需调用的 PlayX 接口(外部,由 PlayX 提供)
|
||||||
|
以下为商城侧调用(由 PlayX 提供)。
|
||||||
|
|
||||||
|
#### Token Verification API
|
||||||
|
* 方法:`POST`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.token_verify_url}`(默认 `/api/v1/auth/verify-token`)
|
||||||
|
|
||||||
|
##### 请求 Body
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 服务端生成,如 `mall_{uniqid}` |
|
||||||
|
| `token` | string | 是 | 前端传入的 PlayX token |
|
||||||
|
|
||||||
|
##### 返回(期望)
|
||||||
|
HTTP 状态码 `200`,响应体需包含:
|
||||||
|
* `user_id`(必选)
|
||||||
|
* `username`(可选)
|
||||||
|
* `token_expire_at`(可选,能被 `strtotime` 解析)
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request_id": "mall_abc123",
|
||||||
|
"token": "PLAYX_TOKEN_XXX"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id": "U123",
|
||||||
|
"username": "demo_user",
|
||||||
|
"token_expire_at": "2026-04-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Bonus Grant API
|
||||||
|
* 方法:`POST`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.bonus_grant_url}`(默认 `/api/v1/bonus/grant`)
|
||||||
|
|
||||||
|
##### 请求 Body
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 如 `mall_bonus_{uniqid}` |
|
||||||
|
| `externalTransactionId` | string | 是 | 订单幂等键:`external_transaction_id` |
|
||||||
|
| `user_id` | string | 是 | PlayX 用户 ID |
|
||||||
|
| `amount` | number | 是 | 订单金额:`MallPlayxOrder.amount` |
|
||||||
|
| `rewardName` | string | 是 | `mall_item.title` |
|
||||||
|
| `category` | string | 是 | `mall_item.category`,默认 `daily` |
|
||||||
|
| `categoryTitle` | string | 是 | `mall_item.category_title` |
|
||||||
|
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
|
||||||
|
|
||||||
|
##### 示例(请求)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request_id": "mall_bonus_abc123",
|
||||||
|
"externalTransactionId": "BONUS_ORD2026....",
|
||||||
|
"user_id": "U123",
|
||||||
|
"amount": 10.0,
|
||||||
|
"rewardName": "每日红利",
|
||||||
|
"category": "daily",
|
||||||
|
"categoryTitle": "每日",
|
||||||
|
"multiplier": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 返回(期望)
|
||||||
|
商城侧以 `data.status` 判断:
|
||||||
|
* `status = "accepted"`:读取 `playx_transaction_id`
|
||||||
|
* 否则:读取 `message` 写入 `fail_reason`
|
||||||
|
|
||||||
|
示例(accepted):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "accepted",
|
||||||
|
"playx_transaction_id": "PX_TX_001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例(reject):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "rejected",
|
||||||
|
"message": "insufficient balance"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Balance Credit API
|
||||||
|
* 方法:`POST`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.balance_credit_url}`(默认 `/api/v1/balance/credit`)
|
||||||
|
|
||||||
|
##### 请求 Body
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `request_id` | string | 是 | 如 `mall_withdraw_{uniqid}` |
|
||||||
|
| `externalTransactionId` | string | 是 | 订单幂等键:`external_transaction_id` |
|
||||||
|
| `user_id` | string | 是 | PlayX 用户 ID |
|
||||||
|
| `amount` | number | 是 | 订单金额:`MallPlayxOrder.amount` |
|
||||||
|
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
|
||||||
|
|
||||||
|
##### 返回(期望)
|
||||||
|
与 Bonus Grant 一致:
|
||||||
|
* 成功:`status="accepted"` + `playx_transaction_id`
|
||||||
|
* 失败:`message` 写入 `fail_reason`
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
请求(示意,由商城侧发起,实际 URL 以配置为准):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request_id": "mall_withdraw_abc123",
|
||||||
|
"externalTransactionId": "WITHDRAW_ORD2026....",
|
||||||
|
"user_id": "U123",
|
||||||
|
"amount": 100.0,
|
||||||
|
"multiplier": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(accepted):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "accepted",
|
||||||
|
"playx_transaction_id": "PX_TX_002"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应(rejected):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "rejected",
|
||||||
|
"message": "insufficient balance"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Transaction Status Query API(交易终态查询)
|
||||||
|
* 方法:`GET`
|
||||||
|
* URL:`${playx.api.base_url}${playx.api.transaction_status_url}`(默认 `/api/v1/transaction/status`)
|
||||||
|
|
||||||
|
##### Query
|
||||||
|
* `externalTransactionId`:订单幂等键
|
||||||
|
|
||||||
|
##### 示例(请求)
|
||||||
|
```bash
|
||||||
|
curl -G '${playx.api.base_url}/api/v1/transaction/status' \
|
||||||
|
--data-urlencode 'externalTransactionId=BONUS_ORD2026....'
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 返回(期望)
|
||||||
|
商城读取 `data.status`:
|
||||||
|
* `COMPLETED`:设置订单 `status=COMPLETED`
|
||||||
|
* `FAILED` 或 `REJECTED`:设置订单 `status=REJECTED`、`grant_status=FAILED_FINAL`,并退回积分;失败信息取 `data.message`
|
||||||
|
|
||||||
|
示例(completed):
|
||||||
|
```json
|
||||||
|
{ "status": "COMPLETED" }
|
||||||
|
```
|
||||||
|
|
||||||
|
示例(failed):
|
||||||
|
```json
|
||||||
|
{ "status": "FAILED", "message": "grant rejected by PlayX" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.3 商城内部 API(供 H5 前端调用)
|
||||||
|
#### Token 验证
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/verify-token`
|
||||||
|
|
||||||
|
请求(Body):
|
||||||
|
* `token`(必填,优先读取)
|
||||||
|
* 兼容:`session`(当 `token` 为空时当作 token 使用)
|
||||||
|
|
||||||
|
成功返回(data):
|
||||||
|
* `session_id`
|
||||||
|
* `user_id`
|
||||||
|
* `username`
|
||||||
|
* `token_expire_at`(ISO 时间字符串)
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/verify-token' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'token=PLAYX_TOKEN_XXX'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 用户资产
|
||||||
|
* 方法:`GET`
|
||||||
|
* 路径:`/api/v1/playx/assets`
|
||||||
|
|
||||||
|
请求参数(二选一):
|
||||||
|
* `session_id`(优先):从 `mall_playx_session` 查 user_id(并校验过期)
|
||||||
|
* `user_id`:直接使用(兼容)
|
||||||
|
|
||||||
|
成功返回(data):
|
||||||
|
* `locked_points`
|
||||||
|
* `available_points`
|
||||||
|
* `today_limit`
|
||||||
|
* `today_claimed`
|
||||||
|
* `withdrawable_cash`(`available_points * points_to_cash_ratio`,保留 2 位)
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
```bash
|
||||||
|
curl -G 'http://localhost:1818/api/v1/playx/assets' --data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"locked_points": 100,
|
||||||
|
"available_points": 50,
|
||||||
|
"today_limit": 200,
|
||||||
|
"today_claimed": 80,
|
||||||
|
"withdrawable_cash": 5.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 领取(Claim)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/claim`
|
||||||
|
|
||||||
|
请求:
|
||||||
|
* `claim_request_id`:幂等键(string,必填且唯一)
|
||||||
|
* 鉴权:`session_id` 或 `user_id`
|
||||||
|
|
||||||
|
成功返回(data):与资产接口一致(`locked_points/available_points/today_limit/today_claimed/withdrawable_cash`)
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
(首次领取成功,可能返回 `msg=Claim success`;若幂等重复,`msg` 可能为空)
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/claim' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'claim_request_id=claim_001' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Claim success",
|
||||||
|
"data": {
|
||||||
|
"locked_points": 60,
|
||||||
|
"available_points": 90,
|
||||||
|
"today_limit": 200,
|
||||||
|
"today_claimed": 120,
|
||||||
|
"withdrawable_cash": 9.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 商品列表
|
||||||
|
* 方法:`GET`
|
||||||
|
* 路径:`/api/v1/playx/items`
|
||||||
|
|
||||||
|
请求(可选):
|
||||||
|
* `type=BONUS|PHYSICAL|WITHDRAW`
|
||||||
|
|
||||||
|
成功返回(data):
|
||||||
|
* `list`:`mall_item` 列表(包含 `amount/multiplier/category/category_title` 等字段)
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
```bash
|
||||||
|
curl -G 'http://localhost:1818/api/v1/playx/items' --data-urlencode 'type=BONUS'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"title": "每日红利",
|
||||||
|
"type": 1,
|
||||||
|
"score": 100,
|
||||||
|
"amount": 10.0,
|
||||||
|
"multiplier": 1,
|
||||||
|
"category": "daily",
|
||||||
|
"category_title": "每日"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 红利兑换(Bonus Redeem)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/bonus/redeem`
|
||||||
|
|
||||||
|
请求:
|
||||||
|
* `item_id`:商品 ID(BONUS)
|
||||||
|
* 鉴权:`session_id` 或 `user_id`
|
||||||
|
|
||||||
|
成功返回(data):
|
||||||
|
* `order_id`
|
||||||
|
* `status`:`PENDING`
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/bonus/redeem' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'item_id=123' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Redeem submitted, please wait about 10 minutes",
|
||||||
|
"data": {
|
||||||
|
"order_id": 456,
|
||||||
|
"status": "PENDING"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 实物兑换(Physical Redeem)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/physical/redeem`
|
||||||
|
|
||||||
|
请求:
|
||||||
|
* `item_id`:商品 ID(PHYSICAL)
|
||||||
|
* `receiver_name`
|
||||||
|
* `receiver_phone`
|
||||||
|
* `receiver_address`
|
||||||
|
* 鉴权:`session_id` 或 `user_id`
|
||||||
|
|
||||||
|
成功返回:
|
||||||
|
* `msg`:`Redeem success`
|
||||||
|
* `data`:null
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/physical/redeem' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'item_id=200' \
|
||||||
|
--data-urlencode 'receiver_name=张三' \
|
||||||
|
--data-urlencode 'receiver_phone=18800001111' \
|
||||||
|
--data-urlencode 'receiver_address=北京市朝阳区XX路XX号' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Redeem success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 提现申请(Withdraw Apply)
|
||||||
|
* 方法:`POST`
|
||||||
|
* 路径:`/api/v1/playx/withdraw/apply`
|
||||||
|
|
||||||
|
请求:
|
||||||
|
* `item_id`:商品 ID(WITHDRAW)
|
||||||
|
* 鉴权:`session_id` 或 `user_id`
|
||||||
|
|
||||||
|
成功返回(data):
|
||||||
|
* `order_id`
|
||||||
|
* `status`:`PENDING`
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
```bash
|
||||||
|
curl -X POST 'http://localhost:1818/api/v1/playx/withdraw/apply' \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'item_id=321' \
|
||||||
|
--data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "Withdraw submitted, please wait about 10 minutes",
|
||||||
|
"data": {
|
||||||
|
"order_id": 789,
|
||||||
|
"status": "PENDING"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 订单列表
|
||||||
|
* 方法:`GET`
|
||||||
|
* 路径:`/api/v1/playx/orders`
|
||||||
|
|
||||||
|
请求:
|
||||||
|
* `session_id` 或 `user_id`
|
||||||
|
|
||||||
|
成功返回(data):
|
||||||
|
* `list`:订单列表(最多 100 条,包含关联的 `mallItem`)
|
||||||
|
|
||||||
|
##### 示例
|
||||||
|
```bash
|
||||||
|
curl -G 'http://localhost:1818/api/v1/playx/orders' --data-urlencode 'session_id=7b1c....'
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 456,
|
||||||
|
"type": "BONUS",
|
||||||
|
"status": "PENDING",
|
||||||
|
"mall_item_id": 123,
|
||||||
|
"points_cost": 100,
|
||||||
|
"amount": 10.0,
|
||||||
|
"external_transaction_id": "BONUS_ORD2026....",
|
||||||
|
"grant_status": "NOT_SENT",
|
||||||
|
"mallItem": { "id": 123, "title": "每日红利", "type": 1 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 同步额度
|
||||||
|
当前代码未实现并未注册路由:`/api/v1/playx/sync-limit`。
|
||||||
|
如需补齐,请在接口设计阶段新增对应实现与 PlayX API 对接。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、后台修改
|
||||||
|
|
||||||
|
### 2.1 商品管理(mall_item)
|
||||||
|
|
||||||
|
- **类型**:需与文档一致
|
||||||
|
- `1` = BONUS(红利)
|
||||||
|
- `2` = PHYSICAL(实物)
|
||||||
|
- `3` = WITHDRAW(提现档位)
|
||||||
|
- **新增字段**(红利/提现档位):
|
||||||
|
- `amount`:现金面值(元)
|
||||||
|
- `multiplier`:流水倍数
|
||||||
|
- `category`:红利业务类别(如 daily)
|
||||||
|
- `category_title`:类别展示名
|
||||||
|
- **库存**:实物需 `stock`;红利/提现可不限制或按业务配置
|
||||||
|
|
||||||
|
### 2.2 订单管理(统一订单表)
|
||||||
|
|
||||||
|
- **订单类型**:BONUS / PHYSICAL / WITHDRAW
|
||||||
|
- **订单状态**:PENDING / COMPLETED / SHIPPED / REJECTED
|
||||||
|
- **实物订单**:
|
||||||
|
- 发货:录入物流公司、单号 → `SHIPPED`
|
||||||
|
- 驳回:录入驳回原因 → `REJECTED`,自动退回积分
|
||||||
|
- **红利/提现订单**:
|
||||||
|
- 展示 `external_transaction_id`、`playx_transaction_id`、发放子状态
|
||||||
|
- 手动重试:仅对 `FAILED_RETRYABLE` 状态,记录 `retry_request_id`、操作者、原因
|
||||||
|
|
||||||
|
### 2.3 用户资产与人工调账
|
||||||
|
|
||||||
|
- **用户资产**:按 `user_id` 展示 `locked_points`、`available_points`、`today_limit`、`today_claimed`
|
||||||
|
- **人工调账**:针对 T+1 推送异常或客诉,支持对 `locked_points`、`available_points` 手动加减,并记录审计日志
|
||||||
|
|
||||||
|
### 2.4 每日推送数据
|
||||||
|
|
||||||
|
- 后台可查看 `mall_playx_daily_push` 表数据,按 `user_id`、`date` 查询,便于排查异常
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、数据库修改
|
||||||
|
|
||||||
|
### 3.1 用户资产表(改造 mall_player 或新建)
|
||||||
|
|
||||||
|
**方案 A:改造 mall_player**
|
||||||
|
|
||||||
|
- 将 `user_id` 作为主键(PlayX 的 user_id)或与现有 id 并存
|
||||||
|
- 新增字段:
|
||||||
|
- `locked_points`(待领取积分)
|
||||||
|
- `available_points`(可用积分)
|
||||||
|
- `today_limit`(今日可领取上限)
|
||||||
|
- `today_claimed`(今日已领取)
|
||||||
|
- `today_limit_date`(今日上限所属日期,用于每日重置)
|
||||||
|
|
||||||
|
**方案 B:新建 mall_playx_user_asset**
|
||||||
|
|
||||||
|
- `user_id`(PlayX 用户 ID,主键或唯一)
|
||||||
|
- `username`(冗余展示)
|
||||||
|
- `locked_points`、`available_points`、`today_limit`、`today_claimed`、`today_limit_date`
|
||||||
|
- `create_time`、`update_time`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 每日推送数据表(新建)
|
||||||
|
|
||||||
|
**表名**:`mall_playx_daily_push`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | int | 主键 |
|
||||||
|
| user_id | varchar(64) | 玩家 ID |
|
||||||
|
| date | date | 业务日期 |
|
||||||
|
| username | varchar(100) | 展示名 |
|
||||||
|
| yesterday_win_loss_net | decimal(15,2) | 昨日净输赢 |
|
||||||
|
| yesterday_total_deposit | decimal(15,2) | 昨日总充值 |
|
||||||
|
| lifetime_total_deposit | decimal(15,2) | 历史总充值 |
|
||||||
|
| lifetime_total_withdraw | decimal(15,2) | 历史总提现 |
|
||||||
|
| create_time | bigint | 创建时间 |
|
||||||
|
|
||||||
|
**唯一索引**:`(user_id, date)` 幂等去重
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 领取记录表(幂等)
|
||||||
|
|
||||||
|
**表名**:`mall_playx_claim_log`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | int | 主键 |
|
||||||
|
| claim_request_id | varchar(64) | 幂等键,唯一 |
|
||||||
|
| user_id | varchar(64) | 用户 ID |
|
||||||
|
| claimed_amount | int | 领取积分 |
|
||||||
|
| create_time | bigint | 创建时间 |
|
||||||
|
|
||||||
|
**唯一索引**:`claim_request_id`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 统一订单表(改造或新建)
|
||||||
|
|
||||||
|
**表名**:`mall_playx_order`(或统一改造 mall_pints_order / mall_redemption_order)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | int | 主键 |
|
||||||
|
| user_id | varchar(64) | 用户 ID |
|
||||||
|
| type | enum | BONUS / PHYSICAL / WITHDRAW |
|
||||||
|
| status | enum | PENDING / COMPLETED / SHIPPED / REJECTED |
|
||||||
|
| mall_item_id | int | 商品 ID |
|
||||||
|
| points_cost | int | 消耗积分 |
|
||||||
|
| amount | decimal(15,2) | 现金面值(红利/提现) |
|
||||||
|
| multiplier | int | 流水倍数 |
|
||||||
|
| external_transaction_id | varchar(64) | 外部交易幂等键 |
|
||||||
|
| playx_transaction_id | varchar(64) | PlayX 流水号 |
|
||||||
|
| grant_status | enum | NOT_SENT / SENT_PENDING / ACCEPTED / FAILED_RETRYABLE / FAILED_FINAL |
|
||||||
|
| fail_reason | text | 失败原因 |
|
||||||
|
| retry_count | int | 重试次数 |
|
||||||
|
| reject_reason | varchar(255) | 驳回原因(实物) |
|
||||||
|
| shipping_company | varchar(50) | 物流公司 |
|
||||||
|
| shipping_no | varchar(64) | 物流单号 |
|
||||||
|
| receiver_name | varchar(50) | 收货人 |
|
||||||
|
| receiver_phone | varchar(20) | 收货电话 |
|
||||||
|
| receiver_address | text | 收货地址 |
|
||||||
|
| create_time | bigint | 创建时间 |
|
||||||
|
| update_time | bigint | 更新时间 |
|
||||||
|
|
||||||
|
**索引**:`user_id`、`external_transaction_id`、`type`、`status`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 商品表(mall_item)扩展
|
||||||
|
|
||||||
|
新增字段(若不存在):
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| amount | decimal(15,2) | 现金面值(红利/提现档位) |
|
||||||
|
| multiplier | int | 流水倍数 |
|
||||||
|
| category | varchar(32) | 红利业务类别 |
|
||||||
|
| category_title | varchar(64) | 类别展示名 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、业务规则
|
||||||
|
|
||||||
|
### 4.1 计算规则(需配置)
|
||||||
|
|
||||||
|
- **返还比例**:`新增保障金 = ABS(yesterday_win_loss_net) * 返还比例`(仅 `yesterday_win_loss_net < 0` 时)
|
||||||
|
- **解锁比例**:`今日可领取上限 = yesterday_total_deposit * 解锁比例`
|
||||||
|
- **提现折算**:积分 → 现金(如 10 分 = 1 元),用于前端展示
|
||||||
|
|
||||||
|
### 4.2 每日重置
|
||||||
|
|
||||||
|
- `today_claimed` 与 `today_limit` 按业务日重置(`today_limit_date` 变化时)
|
||||||
|
|
||||||
|
### 4.3 发放重试
|
||||||
|
|
||||||
|
- 自动重试:1min / 5min / 15min,最多 3 次,使用同一 `externalTransactionId`
|
||||||
|
- 仅对 `NOT_SENT`、`FAILED_RETRYABLE` 重试
|
||||||
|
- 收到 `accepted` 后不再重试,改轮询交易终态查询 API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、实施顺序建议
|
||||||
|
|
||||||
|
1. **数据库**:新增迁移(`mall_playx_daily_push`、`mall_playx_claim_log`、`mall_playx_order`),扩展 `mall_item`、`mall_player`(或新建资产表)
|
||||||
|
2. **模型**:`MallPlayxDailyPush`、`MallPlayxClaimLog`、`MallPlayxOrder`、扩展 `MallItem`、`MallPlayer`
|
||||||
|
3. **接口**:Daily Push API(含签名校验)→ Token 验证 → 资产/领取 → 商品列表 → 红利/实物/提现 → 订单列表
|
||||||
|
4. **后台**:商品扩展、订单管理(含发货/驳回/重试)、人工调账、每日推送数据查看
|
||||||
|
5. **定时任务**:轮询交易终态、自动重试失败发放
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、待确认事项
|
||||||
|
|
||||||
|
- PlayX 提供的 **Token Verification API**、**Bonus Grant API**、**Balance Credit API**、**交易终态查询 API** 的 URL、鉴权方式、字段最终表
|
||||||
|
- `date` 的时区定义(如 UTC+8)
|
||||||
|
- 返还比例、解锁比例、提现折算的具体数值
|
||||||
|
- 是否启用「同步额度」功能(需 PlayX 提供对应 API)
|
||||||
318
docs/积分商城-内部对接与流程说明.md
Normal file
318
docs/积分商城-内部对接与流程说明.md
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
## 1. 文档目的与读者
|
||||||
|
|
||||||
|
本文件为 **内部使用** 的完整说明,用于:
|
||||||
|
|
||||||
|
- 梳理积分商城与 PlayX 之间的 **全量业务流程**(含当前选型与备选方案)。
|
||||||
|
- 统一后台实现口径(资产计算、订单状态机、幂等、重试、对账)。
|
||||||
|
- 为后续版本扩展(实时 webhook、同步按钮、PlayX 拉取模式)预留空间。
|
||||||
|
|
||||||
|
对外给 PlayX 的精简版请参考:`PlayX-对接文档(积分商城).md`。
|
||||||
|
|
||||||
|
## 2. 当前选型概览(V1.0)
|
||||||
|
|
||||||
|
- **主键标识**:`user_id`(贯穿每日推送、资产、订单、发放接口)。
|
||||||
|
- **集成方式**:
|
||||||
|
- 前端:商城 H5 以 Iframe 嵌入 PlayX,postMessage 传 token/session。
|
||||||
|
- 后端:商城独立服务,通过 REST API 与 PlayX 通讯。
|
||||||
|
- **数据来源**:
|
||||||
|
- 资产池:仅使用 **每日 Cron 推送(T+1)** 的历史输赢与充值数据。
|
||||||
|
- 不引入实时充值/流水 webhook 作为资产来源。
|
||||||
|
- **发放模式**:
|
||||||
|
- 商城在红利/提现下单后,**直接调用 PlayX 发放接口**(Bonus Grant / Balance Credit)。
|
||||||
|
- PlayX 侧每 10 分钟 Cron 执行 5.9 adjustment / 最终入账。
|
||||||
|
- **幂等责任分工(方案 A,与对外文档一致)**:
|
||||||
|
- **PlayX**:对 Bonus Grant / Balance Credit 按 **`externalTransactionId` 严格幂等**——同一单号重复请求不得产生第二笔发放;须返回与首次受理一致或可识别的幂等结果(具体 HTTP 体字段由联调约定)。
|
||||||
|
- **商城**:为每笔红利/提现生成**全局唯一** `externalTransactionId`;收到 HTTP 200 且 `status="accepted"` 后**不再向发放接口重放**;对网络超时等“未知是否受理”场景,可在有限次内重试**同一** `externalTransactionId`,依赖 PlayX 幂等保证不双发(见第 6 章)。
|
||||||
|
|
||||||
|
## 3. 角色、系统与对象
|
||||||
|
|
||||||
|
### 3.1 角色
|
||||||
|
|
||||||
|
- **会员**:在 PlayX 内通过 Iframe 使用积分商城。
|
||||||
|
- **运营/客服**:使用商城后台管理商品、订单、调账。
|
||||||
|
- **PlayX 平台**:数据源与权益发放执行方。
|
||||||
|
|
||||||
|
### 3.2 主要对象与前端看板展示映射
|
||||||
|
|
||||||
|
- **目前可提现(现金)**:
|
||||||
|
- 前端展示专用虚拟字段,由可用积分按固定比例折算(如:10 分 = 1 元),用于给玩家呈现直观价值,非底层独立资产。
|
||||||
|
- **待领取积分 (LockedPoints)**:
|
||||||
|
- **定义**:基于昨日玩家亏损转化来的“保障池”,未领取前不可消费。
|
||||||
|
- **数据源**:PlayX 每日推送的 `Yesterday Player Win loss`(取绝对值 × 返还比例)。
|
||||||
|
- **可用积分 (AvailablePoints)**:
|
||||||
|
- **定义**:玩家当前拥有的、可立即抵扣兑换和提现的真实积分资产。
|
||||||
|
- **交互消耗**:所有兑换、提现操作均扣减该字段。
|
||||||
|
- **今日可领取上限 (TodayLimit)**:
|
||||||
|
- **定义**:限制玩家当日最多能“挽回”多少积分。
|
||||||
|
- **数据源**:PlayX 每日推送的 `Yesterday Total Deposit`(昨日总充值 × 解锁比例)。
|
||||||
|
- **今日已领取 (TodayClaimed)**:
|
||||||
|
- **定义**:记录玩家当日累计已领取的积分规模,辅助校验上限,每日重置。
|
||||||
|
- **订单 (Order)**:
|
||||||
|
- 类型:BONUS / PHYSICAL / WITHDRAW
|
||||||
|
- 状态:PENDING / COMPLETED / SHIPPED / REJECTED
|
||||||
|
|
||||||
|
## 4. 端到端流程(内部视角)
|
||||||
|
|
||||||
|
### 4.1 登录与会话建立
|
||||||
|
|
||||||
|
1. 会员在 PlayX 点击“积分商城”入口。
|
||||||
|
2. 父页面加载 Iframe,商城前端进入“连接中/鉴权中”态。
|
||||||
|
3. PlayX 前端通过 postMessage 发送:
|
||||||
|
- `token` 或 session 标识。
|
||||||
|
4. 商城前端将 token 传给后端,后端调用 PlayX 的 **Token Verification API** 验证身份:
|
||||||
|
- **请求参数**:
|
||||||
|
- `token`:接收到的用户会话凭证。
|
||||||
|
- `request_id`:系统发起的鉴权校验流水号。
|
||||||
|
- **响应参数**:
|
||||||
|
- `user_id`:解析出的玩家在平台的唯一标识主键。
|
||||||
|
- `username`:仅用于前端展示的昵称。
|
||||||
|
- `token_expire_at`:用于判断并在濒临过期时触发换取新 Token(无感续期)。
|
||||||
|
5. 商城根据 `user_id` 加载本地资产与订单数据,渲染首页。
|
||||||
|
|
||||||
|
**安全与会话续期要点**:
|
||||||
|
|
||||||
|
- **安全拦截**:不信任前端传入的 `user_id`,必须通过 Token Verification API 获取可信 `user_id`。
|
||||||
|
- **Token 续期**:如果用户在商城停留过久导致 Token 过期(接口返回 `401 / INVALID_TOKEN`),商城前端需通过 `postMessage` 通知 PlayX 父页面重新派发新 Token 以实现静默续期;若无法自动续期,则需弹窗引导用户重新进入商城。
|
||||||
|
|
||||||
|
### 4.2 每日 T+1 数据推送与资产入池
|
||||||
|
|
||||||
|
数据来源:PlayX 每日 Cron。
|
||||||
|
|
||||||
|
- **交互字段说明(T+1 核心输入)**:
|
||||||
|
- `date`:归属业务定日(用于限定该批数据的生效周期)。
|
||||||
|
- `user_id`:玩家平台唯一标识主键。
|
||||||
|
- `yesterday_win_loss_net`:昨日净输赢金额(核心数据源:负数绝对值作为计算新增保障积分类的基准)。
|
||||||
|
- `yesterday_total_deposit`:昨日总充值(核心数据源:作为计算今日领取该积分上限的阈值基准)。
|
||||||
|
- `lifetime_total_deposit` / `lifetime_total_withdraw` / `username`:非运算强制性字段,用于冗余显示或阶层判断预留。
|
||||||
|
|
||||||
|
商城处理逻辑:
|
||||||
|
|
||||||
|
1. 按 `user_id + date` 幂等入库,避免重复处理。(**注:需与 PlayX 明确 `date` 的时区定义,如 UTC+8 等**)。
|
||||||
|
2. 根据业务规则计算:
|
||||||
|
- `新增保障金 = ABS(yesterday_win_loss_net) * 返还比例`(仅当 yesterday_win_loss_net < 0 时产生)。
|
||||||
|
- `今日可领取上限 = yesterday_total_deposit * 解锁比例`。**注意:今日上限每日独立计算,不结转至次日。**
|
||||||
|
3. 将新增保障金累加到 `LockedPoints`,更新 `TodayLimit`。
|
||||||
|
|
||||||
|
**数据修正机制**:
|
||||||
|
|
||||||
|
- T+1 推送不支持覆盖更新(冲正)。如果 PlayX 上游数据算错导致推送有误,商城在二次推送时会触发去重拦截(`deduped`)。此类异常数据统一由**商城后台人工调账**处理。
|
||||||
|
|
||||||
|
### 4.3 领取流程(从待挽回转化为可用)
|
||||||
|
|
||||||
|
触发:会员在首页核心看板上查看领取进度(基于 `昨日充值` 计算),如果进度符合预期,点击“立即领取”。
|
||||||
|
|
||||||
|
- **前端交互**:
|
||||||
|
- 判断逻辑:若 `昨日充值`(即 TodayLimit)均不足导致上限不够,按钮可能置灰并提示“昨日存款不足,无法全额领取”。
|
||||||
|
- 操作反馈:点击后从底部呼出二次确认层(“将待领取积分划转为可用积分后,即可兑换或提现。确定领取?”)。
|
||||||
|
- 确认层点击“确定”后发出后端请求。
|
||||||
|
- **后端校验条件**:
|
||||||
|
- `LockedPoints > 0`
|
||||||
|
- `TodayLimit - TodayClaimed > 0`
|
||||||
|
- **后端计算与执行**:
|
||||||
|
- `canClaim = min(LockedPoints, TodayLimit - TodayClaimed)`
|
||||||
|
- **原子更新数据库**:
|
||||||
|
- `LockedPoints -= canClaim`
|
||||||
|
- `AvailablePoints += canClaim`
|
||||||
|
- `TodayClaimed += canClaim`
|
||||||
|
- **防重与幂等**:
|
||||||
|
- 接口入参包含 `claim_request_id`,同一 `claim_request_id` 重复提交不重复扣减。
|
||||||
|
- **前端成功响应**:
|
||||||
|
- 后端处理完成后,前端弹出“领取成功,积分已到账”状态框。
|
||||||
|
- **即时刷新**看板中的 `待领取积分` 与 `可用积分` 与 `目前可提现现金`。
|
||||||
|
- **积分有效期说明**:
|
||||||
|
- V1.0 版本积分无有效期限制,`LockedPoints` 和 `AvailablePoints` 永久有效并持续累加,后续视财务成本压力再引入年底清零/周期过期机制。
|
||||||
|
|
||||||
|
### 4.4 红利兑换(BONUS)
|
||||||
|
|
||||||
|
1. 会员在前端选择红利商品(配置来自后台商品管理)。
|
||||||
|
2. 前端展示二次确认弹层(金额、流水倍数、说明),**并需在 UI 上显式提示会员:“发放预计需等待约 10 分钟内到账”**。
|
||||||
|
3. 提交后,前端进入“兑换中”类似状态,**不可直接提示“兑换成功”**,随后商城后端执行:
|
||||||
|
- 校验积分余额是否足够。
|
||||||
|
- 校验商品状态(上架、库存)。
|
||||||
|
- 校验订单状态(防重复提交)。
|
||||||
|
4. 创建订单:
|
||||||
|
- `type = BONUS`
|
||||||
|
- `status = PENDING`
|
||||||
|
- `user_id`
|
||||||
|
- 商品信息 & 消耗积分
|
||||||
|
5. 原子扣减 `AvailablePoints`。
|
||||||
|
6. 生成 `externalTransactionId`(例如 `BONUS_ORD{订单号}`)。
|
||||||
|
7. 调用 PlayX Bonus Grant API(核心透传字段,详见外部对接文档):
|
||||||
|
- `externalTransactionId`:本单派发流水号(**防重**:用于要求下游强制幂等拦截)。
|
||||||
|
- `user_id`:玩家标示。
|
||||||
|
- `amount`:红利发放切实的现金面值。
|
||||||
|
- `multiplier`:这笔款项后续提款的流水约束倍数。
|
||||||
|
- `rewardName`:展示给玩家此笔红利来源名称。
|
||||||
|
- `category`:便于平台统计对账的红利业务大类。
|
||||||
|
8. PlayX 返回:
|
||||||
|
- 若 HTTP 200 且 `status = "accepted"`:
|
||||||
|
- 记录 `playx_transaction_id`。
|
||||||
|
- 订单保持 PENDING(等待对方系统最终发放)。
|
||||||
|
- **发起请求动作不再重试**。但商城侧需定时调用 PlayX 提供的 **交易状态查询 API** 轮询确认该订单最终结果,成功则转 `COMPLETED`,失败则转 `REJECTED` 并退分。
|
||||||
|
- 否则:
|
||||||
|
- 记录失败原因。
|
||||||
|
- 进入“可重试队列”(自动/人工重试,见第 6 章)。
|
||||||
|
|
||||||
|
### 4.5 实物兑换(PHYSICAL)
|
||||||
|
|
||||||
|
1. 会员选择实物商品并填写收货信息。
|
||||||
|
2. 商城后端:
|
||||||
|
- 校验库存/积分。
|
||||||
|
- 创建 PENDING 订单,扣减 `AvailablePoints`。
|
||||||
|
3. 后台处理:
|
||||||
|
- 发货:录入物流公司与单号,状态 → SHIPPED。**(可选:调用 PlayX Inbox API 给用户发送发货通知站内信)**。
|
||||||
|
- 驳回:录入驳回原因,状态 → REJECTED,并退回积分。**(可选:调用 PlayX Inbox API 告知用户驳回原因)**。
|
||||||
|
|
||||||
|
### 4.6 提现回平台余额(WITHDRAW 操作逻辑)
|
||||||
|
|
||||||
|
本流程旨在将兑换所得的虚拟积分,按照规定“提现”为充入 PlayX 平台账户的真实金额。
|
||||||
|
|
||||||
|
1. **前端选择与展示**:
|
||||||
|
- 会员在首页或“提现到平台”类别列表中选择特定提现档位(如:提现 100 元,需要 1000 积分,1倍流水要求)。
|
||||||
|
- 点击“提现”按钮后,前端唤出底部二次确认弹层。
|
||||||
|
- 弹层内容展示所选档位**所需消耗的积分值**以及对应的**流水要求倍数**,等待二次确认提交。
|
||||||
|
- (**前端同时需在弹层或说明中向用户提示:提现申请预计约 10 分钟在平台入账**)。
|
||||||
|
2. **后端可用性校验**:
|
||||||
|
- 用户确认后,前端请求后端。商城后端比较当前用户的 `AvailablePoints` 是否 `>= item.points`。如果不足,阻断流程并返回前端 `积分不足` 的报错浮层。
|
||||||
|
3. **资产扣减与订单落库**:
|
||||||
|
- 原子扣减数据库内该会员的 `AvailablePoints`。
|
||||||
|
- 创建 WITHDRAW 暂挂订单(状态 `PENDING`),记录该单提现对应的 `amount` 与 `multiplier`。
|
||||||
|
- 生成外部交易单号 `externalTransactionId`(如 `WITHDRAW_ORD{订单号}`)。
|
||||||
|
4. **调用 PlayX API 发放(核心参数解析)**:
|
||||||
|
- 商城发包调用 PlayX 的 Balance Credit API:
|
||||||
|
- `externalTransactionId`:本提现申请的订单号(**提现防重唯一拦截键**)。
|
||||||
|
- `user_id`:发起提现的玩家 ID。
|
||||||
|
- `amount`:要充入平台余额池的真金面值。
|
||||||
|
- `multiplier/turnover_rule`:该笔提现资金入账后锁定的打码流水倍数条件。
|
||||||
|
5. **异步等待终态与 UI 回显**:
|
||||||
|
- 收到 API `accepted` 响应后,商城将不间断返回前端“提交成功,预计 10 分钟内处理”。
|
||||||
|
- 商城内部保持该订单为 PENDING,并进入定时轮询状态,监控 PlayX 10 分钟 Cron 执行后的“最终业务发货结果”,闭环完成后才转入 COMPLETED 或对失败按规则退分。
|
||||||
|
|
||||||
|
## 5. 扩展与备选方案(暂不对外)
|
||||||
|
|
||||||
|
本章为 **扩展设计/备选方案**,当前版本不对 PlayX 提出实现要求,只在内部保留。
|
||||||
|
|
||||||
|
### 5.1 实时充值 webhook(备选)
|
||||||
|
|
||||||
|
用途:让“今日可领取上限”不仅依赖 T+1 数据,还能实时响应充值行为。
|
||||||
|
|
||||||
|
示例设计:
|
||||||
|
|
||||||
|
- PlayX 在充值成功后,调用商城的充值 webhook:
|
||||||
|
- 字段:`user_id`、`amount`、`transaction_id`、`occurred_at`。
|
||||||
|
- 商城:
|
||||||
|
- 以 `transaction_id` 幂等入库。
|
||||||
|
- 更新“当日实时充值统计”,供风控或前端展示使用。
|
||||||
|
|
||||||
|
当前状态:
|
||||||
|
|
||||||
|
- V1.0 不启用此对接;所有领取逻辑仅基于每日推送(T+1)。
|
||||||
|
|
||||||
|
### 5.2 外部积分来源 webhook(任务/轮盘)
|
||||||
|
|
||||||
|
用途:把 PlayX 任务、幸运轮盘等活动产出的积分汇总到商城。
|
||||||
|
|
||||||
|
示例设计:
|
||||||
|
|
||||||
|
- PlayX 调用 webhook:
|
||||||
|
- 字段:`user_id`、`points`、`source`、`transaction_id`。
|
||||||
|
- 商城:
|
||||||
|
- 以 `transaction_id` 幂等增加 `AvailablePoints` 或某个“活动积分池”。
|
||||||
|
|
||||||
|
当前状态:
|
||||||
|
|
||||||
|
- V1.0 不做接入;防止资产口径复杂化。
|
||||||
|
|
||||||
|
### 5.3 “同步额度”交互逻辑(手动拉取实时充值)
|
||||||
|
|
||||||
|
**功能目的**:由于积分商城的基础计算依赖的是 T+1 每日推送,如果不做任何补充,今日充值的玩家将无法即刻提升“今日可领取上限(TodayLimit)”。因此原型中设置了“同步额度”按钮,作为手动拉取实施更新的一种补救路径。
|
||||||
|
|
||||||
|
**前端交互逻辑**:
|
||||||
|
- 用户在首页面板点击“同步额度”次要操作按钮。
|
||||||
|
- 前端短暂 loading,调用后端接口拉取后,弹出轻量级反馈:“已同步最新额度”。
|
||||||
|
- 页面数据看板数字重新渲染刷新。
|
||||||
|
|
||||||
|
**后端业务逻辑选型推荐**:
|
||||||
|
|
||||||
|
- **方案 A(建议,商城做拉取请求)**:
|
||||||
|
- 会员点击同步按钮,商城后端拦截并向 PlayX 系统调用一个**“查询今日实时余额/重算存款 API”**。
|
||||||
|
- 获取到最新存款后,累加或覆盖当前的 `TodayLimit`。
|
||||||
|
- **方案 B(PlayX 控制权)**:
|
||||||
|
- 点击后,通过 iframe 的 `postMessage` 向父级 PlayX 窗口发送同步指令。
|
||||||
|
- PlayX 在自身域内统计今日所有游戏存款流水进行汇总归集(甚至一键转账入主钱包的操作)。
|
||||||
|
- 处理完成后 PlayX 主动请求积分商城的`更新 webhook`来给 `TodayLimit` 加额,最后前端获取成功事件刷新。
|
||||||
|
- **当前定案落地方向**:
|
||||||
|
- 根据原型要求,本功能**必须落地**。推荐使用方案 A。商城侧需准备一个接受通知的 API,或 PlayX 需要支持实时提供玩家当日总存款的只读 API,以供点击拉取。对于 V1.0 对外文档里若不打算实现,需与 PlayX 进一步交涉决定是否阉割掉此按钮。
|
||||||
|
|
||||||
|
### 5.4 发放模式备选:PlayX 定时拉取
|
||||||
|
|
||||||
|
备选方案:
|
||||||
|
|
||||||
|
- 由 PlayX 每 10 分钟调用商城查询接口,拉取待发放订单列表,然后自行发放。
|
||||||
|
|
||||||
|
需要新增:
|
||||||
|
|
||||||
|
- 商城提供“待发放订单列表”接口(分页、过滤、幂等标记)。
|
||||||
|
- 双方需要就“已拉取但未发放”、“重复拉取”等边界做严格定义。
|
||||||
|
|
||||||
|
当前选型:
|
||||||
|
|
||||||
|
- 考虑到复杂度与 PlayX 当前发放系统形态,最终选择 **商城主动调用 PlayX 发放 API** 的模式,拉取模式仅保留在内部文档中作为备选。
|
||||||
|
|
||||||
|
## 6. 幂等、重试与状态机(内部实现口径)
|
||||||
|
|
||||||
|
### 6.1 幂等键
|
||||||
|
|
||||||
|
- 每日推送:`user_id + date`。
|
||||||
|
- 充值/外部积分 webhook(如启用):`transaction_id`。
|
||||||
|
- 领取:`claim_request_id`。
|
||||||
|
- 红利/提现发放:`externalTransactionId`。
|
||||||
|
|
||||||
|
### 6.2 发放请求状态机(商城内部)
|
||||||
|
|
||||||
|
针对每个订单(BONUS/WITHDRAW),在商城内部维护发放子状态,例如:
|
||||||
|
|
||||||
|
- `NOT_SENT`:未发送给 PlayX。
|
||||||
|
- `SENT_PENDING`:已发送,等待 PlayX 响应。
|
||||||
|
- `ACCEPTED`:收到 HTTP 200 且 `status = "accepted"`。
|
||||||
|
- `FAILED_RETRYABLE`:失败且可重试(如超时、上游错误)。
|
||||||
|
- `FAILED_FINAL`:最终失败(达到重试上限或 PlayX 返回不可恢复错误)。
|
||||||
|
|
||||||
|
关键规则:
|
||||||
|
|
||||||
|
- 只有在 `NOT_SENT` 或 `FAILED_RETRYABLE` 状态下才允许“再次发送”。
|
||||||
|
- 一旦进入 `ACCEPTED`,不得再发请求(自动或人工)。
|
||||||
|
- 订单业务状态(PENDING/COMPLETED/REJECTED)与发放子状态之间要有清晰映射:
|
||||||
|
- `ACCEPTED` + 对账确认成功 → `COMPLETED`。
|
||||||
|
- `FAILED_FINAL` → `REJECTED`(并退积分)。
|
||||||
|
|
||||||
|
### 6.3 重试策略(内部)与防重底线
|
||||||
|
|
||||||
|
- **前提**:PlayX 已承诺按 **`externalTransactionId` 严格幂等**(方案 A)。在此前提下,读超时后使用**同一** `externalTransactionId` 的有限次自动重试是安全的。
|
||||||
|
- 自动重试:
|
||||||
|
- 仅针对网络错误、Read Timeout(读超时)、`PLAYX_UPSTREAM_ERROR` 等,且**尚未收到** HTTP 200 + `status="accepted"`。
|
||||||
|
- 建议间隔:1min / 5min / 15min,最多 3 次;**每次重试必须使用原订单的同一** `externalTransactionId`,不得生成新单号冒充新单。
|
||||||
|
- 人工重试:
|
||||||
|
- 仅允许在 `FAILED_RETRYABLE` 状态下触发。
|
||||||
|
- 每次需记录 `retry_request_id`、操作者、原因。若自动重试耗尽仍失败,可先通过 **交易终态查询 API** 核对再决定是否人工干预。
|
||||||
|
- 绝不重试场景:
|
||||||
|
- 发放请求已明确收到 `status = "accepted"`(后续只走 5.5 轮询与客诉流程,不向发放接口重放)。
|
||||||
|
- 明确业务拒绝类错误(如参数非法、规则不满足)。
|
||||||
|
|
||||||
|
## 7. 对账与问题排查
|
||||||
|
|
||||||
|
- 对账来源:
|
||||||
|
- 商城订单表(含 externalTransactionId、playx_transaction_id)。
|
||||||
|
- PlayX 提供的对账/流水查询(如有)。
|
||||||
|
- 常见问题场景:
|
||||||
|
- 商城显示 REJECTED 但会员反馈已收到红利:需检查是否在“发放后回滚积分”链路出错。
|
||||||
|
- 商城显示 PENDING 时间过长:需排查 PlayX 侧 10 分钟 Cron 是否正常。
|
||||||
|
|
||||||
|
## 8. 与对外文档的关系
|
||||||
|
|
||||||
|
- 本文档:覆盖“能想到的所有对接与流程设计”,供产品、后端、运营、商务内部统一认识。
|
||||||
|
- `PlayX-对接文档(积分商城).md`:
|
||||||
|
- 仅暴露 PlayX V1.0 必须提供/实现的部分。
|
||||||
|
- 承诺最小闭环,不在主文中提及实时 webhook、同步按钮、拉取模式。
|
||||||
|
- **同步额度等能力**若产品仍要落地,以本文 **5.3** 与商务/PlayX 结论为准;对外文档不写不代表产品一定不做。
|
||||||
|
|
||||||
Reference in New Issue
Block a user