Files
webman-buildadmin-mall/docs/积分商城-内部对接与流程说明.md
2026-03-20 18:11:00 +08:00

319 lines
18 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.
## 1. 文档目的与读者
本文件为 **内部使用** 的完整说明,用于:
- 梳理积分商城与 PlayX 之间的 **全量业务流程**(含当前选型与备选方案)。
- 统一后台实现口径(资产计算、订单状态机、幂等、重试、对账)。
- 为后续版本扩展(实时 webhook、同步按钮、PlayX 拉取模式)预留空间。
对外给 PlayX 的精简版请参考:`PlayX-对接文档(积分商城).md`
## 2. 当前选型概览V1.0
- **主键标识**`user_id`(贯穿每日推送、资产、订单、发放接口)。
- **集成方式**
- 前端:商城 H5 以 Iframe 嵌入 PlayXpostMessage 传 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`
- **方案 BPlayX 控制权)**
- 点击后,通过 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 结论为准;对外文档不写不代表产品一定不做。