## 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 结论为准;对外文档不写不代表产品一定不做。