# 每日幸运补偿接口

**daily_bankrupt_pack**

每日幸运补偿（原名「每日破产礼包」）是**限时触发型**活动：当玩家余额跌到破产线以下、且满足当日累充门槛（门槛为 0 时无需当日充值）时，服务端自动为其生成一个**限时礼包资格（offer）**；玩家在倒计时内充值（`source=daily_bankrupt_pack`）即自动获得加赠，倒计时结束礼包作废。

**本活动没有手动领取入口**：`POST /promotion/daily_bankrupt_pack/claim` 始终返回错误「幸运补偿由充值订单自动发放」，前端**不要**接这个接口。双钱包版本中，满足条件的加赠与充值本金在支付成功后的同一入账事务中处理。

本金计入现金余额 `user_money` 并建立不可放弃的本金任务；加赠有码计入奖金余额 `bonus_money` 并建立奖励任务，无码计入 `user_money`。奖励任务的倍数与放弃权限在发奖时固化，按 [任务列表](201_wager_tasks.md#list) 返回值展示。整个账户须满足 [全部任务与旧打码门槛](400_promotion.md#wager_rules) 才能通过提现打码校验。

礼包资格通过【[站点配置](500_site_content.md#config)】（`GET /site_content/config`）的 `popups` 列表下发，无需额外轮询接口；另在礼包**新生成**时会通过【[实时提示推送（SSE）](510_message.md#realtime-promotion-trigger)】推一条 `type=promotion_trigger` / `data.kind=daily_bankrupt_pack` 的 toast，提示前端去拉取礼包（见下方【[触发条件与生命周期](#lifecycle)】）；`list` / `detail` 走通用活动接口（`kind=daily_bankrupt_pack`，详见 [活动接口](400_promotion.md)）。

---

## 触发条件与生命周期 {#lifecycle}

礼包由服务端在以下**两个时机**判定，同时满足下列条件才会生成：

1. 玩家已登录（游客不触发）
2. 账户总余额 `total`（`user_money + bonus_money`）≤ 商户配置的破产线，不是只看现金余额 `user_money`
3. 当日（**商户时区**自然日）有成功充值记录，且当日累计成功充值 ≥ 累充门槛
4. 玩家**历史累计充值**已达最低累充分层门槛（见下方【[累充分层](#group)】）
5. 当日已生成的礼包数量未超过每日上限

时机 | 说明
---|---
**余额下降后主动判定** | 玩家余额发生下降（投注、提现等）后，服务端等待约 **3 秒无新变动**再判定；命中则即时生成礼包并通过【[实时提示推送](510_message.md#realtime-promotion-trigger)】下发 `type=promotion_trigger` / `data.kind=daily_bankrupt_pack` 的 toast
**`GET /site_content/config` 惰性判定** | 原有逻辑保留，作为**兼容与兵底**（SSE 未连上、toast 丢失、服务重启等情形仍能拿到礼包）

> - 两个时机**不会生成两份礼包**：已持有有效礼包时判定直接返回原礼包，toast 也只在**新生成**的那一刻推一次。
> - 3 秒静默窗口也用于避开「投注扣款已落、派彩未到」的瞬时低点，因此玩家中奖的那一局**不会**误触发。连续投注时计时会被不断重置，**停手后**才判定。
> - **toast 只是提醒，不是数据源**：收到后仍需拉 `GET /site_content/config` 取 `popups` 里的礼包字段；**不得**把 toast 当作礼包存在与否的唯一依据。

生成后礼包状态流转：

状态 | 说明
---|---
`active` | 有效，倒计时未结束，充值可加赠
`used` | 已被一笔充值兑现（同一礼包只能加赠一次）
`expired` | 倒计时结束未兑现，作废

> 礼包一旦生成，即使玩家余额回升，也会持续展示到**兑现或过期**为止；倒计时**可以跨自然日**（如 23:55 生成、次日 00:10 到期），跨天后仍然有效、仍可兑现。

---

## 累充分层 {#group}

商户可按玩家的**历史累计充值金额**划分若干分层（如 `[10,50)`、`[50,100)`、`[100,500)`、`[500,∞)`），**每层拥有完全独立的礼包档位列表、单笔加赠上限与打码倍数**，不同层的玩家看到的礼包内容不同。

- 分层在**礼包生成时就已锁定**（存入 offer）：玩家在倒计时内充值抬高了累计充值，**本笔仍按锁定层发奖**，与弹窗展示一致；升层从下一个礼包开始生效。
- 历史累计充值**未达最低层门槛**的玩家不会生成礼包（不下发 popup，`detail` 的 `offer_status` 为 `none`）。
- 接口下发的 `tiers` / `single_charge_max_bonus` / `recommended_tier_index` **已经是命中层的值**（扁平结构，与改造前一致），前端**无需**自己按累充金额选层；`group_index` / `group_min_recharge` / `group_max_recharge` 仅供文案展示（如「再充 X 解锁更高礼包」）。

---

## 礼包下发字段（站点配置 popup） {#popup}

【[站点配置](500_site_content.md#config)】响应中 `data.popups` 的元素，`kind = daily_bankrupt_pack` 时字段如下（仅在商户开启弹窗且玩家当前持有有效礼包时出现）：

字段 | 类型 | 说明
---|---|---
kind | string | 固定 `daily_bankrupt_pack`
offer_id | string | 礼包资格 ID（Snowflake，字符串处理）
expire_at | string | 礼包过期时刻，**UTC** RFC3339（如 `2026-08-10T09:15:00Z`）
server_now | string | 服务端当前时刻，**UTC** RFC3339；供前端校准倒计时用
today_deposit | string | 触发时统计到的当日累计成功充值金额（十进制字符串，商户时区自然日口径）
total_deposit | string | 触发时的**历史累计充值**快照（十进制字符串），分层依据
seq | number | 该礼包是玩家当日的第几次触发（1-based）
daily_max_offers | number | 每人每日最多可触发的礼包次数（商户后台配置），可用于「今日 1/1 次」类文案
bonus_type | string | 加赠方式：`percent` 按档位门槛比例 / `fixed` 固定金额；仅影响文案展示，金额一律以 `bonus_amount` 为准
group_index | number | 命中的【[累充分层](#group)】下标（0-based，生成礼包时已锁定）
group_min_recharge | number | 本层累充下限（含）
group_max_recharge | number | 本层累充上限（**不含**）；`0` 表示不限（最高层）
tiers | list&lt;Tier&gt; | 充值加赠档位列表（**已是命中分层的档位**），见下表
recommended_tier_index | number | 推荐档位下标（0-based），前端默认选中该档；服务端已归一化越界值，可直接使用
single_charge_max_bonus | number | **本层**单笔加赠上限（元），已应用到各档的 `bonus_amount`，此处仅供规则说明展示
principal_bet_multiple | number | **本金**打码倍数（商户财务全局配置），用于计算总流水要求
description | string | 活动说明文案（商户后台配置，可能为空字符串）

**`Tier` 字段:**

字段 | 类型 | 说明
---|---|---
min_amount | number | 充值门槛（充值面额 ≥ 该值命中该档）
bonus_percent | number | 加赠比例，**百分数**（如 `30` 表示 30%），仅 `bonus_type=percent` 时有意义且仅供文案展示
bonus_amount | string | **实际加赠金额**（十进制字符串），已按单笔上限封顶；展示金额请一律以本字段为准
bonus_capped | bool | 该档加赠是否被单笔上限截断；为 `true` 时 `bonus_amount` 小于按配置算出的理论值，前端不应再展示百分比以免误导
bet_multiple | number | 该档**加赠金额**的打码倍数，已应用商户全局覆盖，即为最终生效值
total_amount | string | 该档展示总额 = `min_amount` + `bonus_amount`（十进制字符串），不是可提现金额，也不是现金余额

> 档位的金额与倍数均为服务端**算好的生效值**，前端直接渲染即可，不需要（也不应）自行按 `bonus_percent` 计算或复刻打码倍数的覆盖规则。
>
> 加赠金额与玩家实际充值金额无关，只看命中的档位：`bonus_type=percent` 时为「档位门槛 × 比例」（`min_amount=100`、`bonus_percent=50` 时，充值 100 或 180 都只加赠 50）；`bonus_type=fixed` 时为该档配置的固定金额。
>
> 档位命中规则：**从高到低**取第一个满足 `充值面额 ≥ min_amount` 的档位；未达最低档不加赠。
>
> **本次新增任务要求的预估** = `实际到账本金 × principal_bet_multiple` + `bonus_amount × bet_multiple`。本金和有码加赠分别建立任务，以任务返回的 `target_amount` 为准；该公式不包含账户此前的待完成任务和旧打码，不能作为全账户提现判据。

---

## 倒计时与过期 {#countdown}

- 剩余时间必须按 **`expire_at` − `server_now`** 计算，**不要使用客户端本地时钟**（玩家设备时区/时间可能不准）。
- 两个时间字段都是 **UTC**，前端如需展示绝对时刻请自行转换到本地或商户时区。
- 服务端发奖时对 `expire_at` 有**秒级宽限**（容忍网络与时钟偏差），但前端不应依赖该宽限；倒计时归零即应收起礼包入口。
- 倒计时结束后再充值**不会**加赠，且该礼包在下次 `GET /site_content/config` 时被置为过期并从 `popups` 中消失。

---

## 前端如何兑现 {#redeem}

1. 从 `GET /site_content/config` 的 `popups` 中取到 `kind=daily_bankrupt_pack` 的礼包，渲染倒计时与档位。收到 `data.kind=daily_bankrupt_pack` 的 [SSE toast](510_message.md#realtime-promotion-trigger) 时也拉一次该接口，**按 `offer_id` 去重**避免重复弹窗。
2. 玩家选择档位后调用【[创建充值订单](200_finance.md#recharge_create)】，**必须传 `source=daily_bankrupt_pack`**，`amount` 传所选档位的 `min_amount`（或更高金额）。
3. 完成支付。服务端处理充值成功时，在同一事务中入账本金和符合条件的加赠；加赠仍不合并到充值订单的本金到账字段中。刷新 [余额](200_finance.md#account_balance) 与 [任务列表](201_wager_tasks.md#list) 确认结果。

> **注意事项：**
>
> - `source` 传错或不传，即使处于倒计时内也**不会**加赠。
> - 支付结果通知可能延迟，不要依赖支付返回页确认到账；以充值订单状态、余额和任务查询为准，不再假设本金到账后还有一次独立异步加赠。
> - 选档基数为**订单面额**（非扣除通道手续费后的到账额），因此按档位门槛下单不会掉档。
> - 同一礼包只兑现一次；兑现后礼包变为 `used`，不再出现在 `popups` 中。

---

## 活动详情字段 {#detail}

通用接口 `GET /promotion/daily_bankrupt_pack/detail` 的 `data` 字段：

字段 | 类型 | 说明
---|---|---
offer_status | string | 当日礼包状态：`none` 无礼包 / `active` 有效 / `used` 已兑现 / `expired` 已过期（`active` 但已超时按 `expired` 返回；刚超时且仍在服务端发奖宽限秒数内时按 `none` 返回，不下发已过期礼包，避免前端弹「已过期」提示）
offer_id | string | 礼包资格 ID；无礼包时为空字符串 `""`
expire_at | string | 礼包过期时刻（**UTC** RFC3339）；无礼包时为 `null`
server_now | string | 服务端当前时刻（**UTC** RFC3339）
today_deposit | string | 触发时统计到的当日累计成功充值金额；无礼包时为 `"0"`
total_deposit | string | 玩家**历史累计充值**；持有礼包时为触发时快照，否则为当前值（未登录为 `"0"`）
seq | number | 该礼包是玩家当日的第几次触发（1-based）；无礼包时为 `0`
daily_max_offers | number | 每人每日最多可触发的礼包次数
bonus_type | string | 加赠方式：`percent` / `fixed`，含义同【[礼包下发字段](#popup)】
group_index | number | 展示所用的【[累充分层](#group)】下标：持有礼包时为礼包锁定的层，否则按当前累充预览（未达最低层/未登录时为 `0`）
group_min_recharge | number | 该层累充下限（含）
group_max_recharge | number | 该层累充上限（**不含**）；`0` 表示不限
tiers | list&lt;Tier&gt; | 加赠档位列表（已是上述分层的档位），字段同【[礼包下发字段](#popup)】
recommended_tier_index | number | 推荐档位下标（0-based）
single_charge_max_bonus | number | **该层**单笔加赠上限（元）
principal_bet_multiple | number | 本金打码倍数（商户财务全局配置）
bankrupt_line | number | 礼包资格破产线：总余额 `total`（`user_money + bonus_money`）≤ 该值时满足余额条件；不是任务清理的固定 `0.1` 阈值
window_minutes | number | 礼包有效时长（分钟）
description | string | 活动说明文案

> 该接口**只读当日最新一条礼包，不会新建礼包**；礼包的生成只发生在 `GET /site_content/config` 上。
>
> 活动列表（`GET /promotion/list`）中本活动仅在商户开启「列表展示」时出现，**与玩家当前是否持有礼包无关**（入口常驻，不会忽隐忽现）；玩家点入后按本接口的 `offer_status` 区分「当前有限时礼包」与「暂无资格，达成条件后自动触发」两种态。未开启列表展示时活动只通过弹窗触达。红点 `has_unclaimed` 恒为 `false`（无手动领取项）。
