# 账号任务奖励

---

`kind=account_tasks`。本期仅开放通知任务 `pwa`，邮箱、手机任务不向玩家展示，也不能领取。

奖励记录按 `config_snapshot.task` 展示具体任务名称：本期 `pwa` 显示「PWA通知」，不以「账号任务奖励」作为已领取奖励名称。打码任务同样按快照的 `task` 展示，缺失时可使用 `source_id`；活动分类与资金来源标识仍保持 `account_tasks`，不改变入账和筛选契约。

## 参与规则 {#rules}

- 奖励金额由商户配置，无固定初始金额；打码倍数默认 1 倍、后台可调整。参与前展示任务对应的实际金额、倍数与规则，不设置共用活动说明。
- 存量账号和游客账号均可参与；游客须有游客登录态，不等于匿名访问。已经开启通知的账号也可领取。
- 每个商户内每账号每项任务终身一次；关闭重开活动、改变配置或重新订阅均不重置。
- 同商户 PWA 通知任务按 IP 和设备号分别累计成功领取账号数，两个上限均须满足，默认各 1 个，商户可调整为 1–100000；终身累计，失败不占次数，关闭重开不重置。原有领取记录缺少 IP／设备信息，不能追溯计入这两个维度。
- 通知授权和领取是两个操作，由一次点击顺序完成。普通订阅续期不发奖；领取成功后隐藏卡片，历史保留。
- 有打码要求的奖励进入奖金余额，无打码要求进入现金余额，沿用[奖励钱包规则](400_promotion.md#wager_rules)。本任务打码任务按不可放弃处理。
- 不设置活动时间、预算上限，不保证物理设备唯一。FCM token 是订阅实例标识，不是设备 ID。

## 活动详情 {#detail}

**URL:** `GET` /promotion/account_tasks/detail

**输入:** 无；匿名可以查询配置，有登录态时查询该账号领取状态。

**输出:**

字段 | 类型 | 说明
---|---|---
data.task | string | 固定 `pwa`
data.status | string | `unclaimed` 未领取且任务启用 / `already_claimed` 已领取 / `disabled` 未启用
data.amount | string \| null | 当前配置金额，未配置为 `null`
data.bet_multiple | int | 当前配置打码倍数
data.claim | object | 已领取时提供历史记录，其他状态不提供
data.claim.id | string | 领取记录 ID
data.claim.merchant_id | string | 商户 ID
data.claim.member_id | string | 会员 ID
data.claim.task | string | 任务标识
data.claim.amount | string | 当时实际发放金额
data.claim.bet_multiple | int | 当时实际生效倍数
data.claim.created_at / updated_at | string | 记录时间

已领取状态优先于活动关闭状态；历史金额以 `claim.amount` 为准，不使用当前配置金额替代。

[活动列表](400_promotion.md#list)的 `data[].payload` 使用相同结构；领取后该账号不再得到此任务卡片。列表是否提供入口还受平台上架、商户活动开关和列表展示开关控制。浏览器能力和通知权限由客户端另外判断，不能作为服务端已认可资格的证据。该任务不增加“可领奖励项数”角标。

---

## 领取通知奖励 {#claim}

**URL:** `POST` /promotion/account_tasks/claim

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
task | string | 是 | 固定 `pwa`；其他任务拒绝
token | string | 是 | Firebase SDK 获取的当前订阅 token，须先通过[订阅接口](111_pwa_push.md#subscribe)上报，最长 4096 字符，无首尾空白
device_id | string | 首次领取是 | 客户端本地持久化的非零 UUID 设备号，沿用登录／注册的设备号；缺失或无效拒绝首次领取，已领取账号仍返回历史结果

**输出:**

字段 | 类型 | 说明
---|---|---
data.status | string | `credited` 本次到账 / `already_claimed` 历史已领取、本次不发奖
data.amount | string | 本次或历史实际发放金额
data.new_reward_granted | bool | 后端生成：首次入账且事务提交成功为 `true`，重复领取为 `false`；不接受客户端控制

首次领取要求活动已开放、当前上报 token 一致，并通过 FCM dry-run 基础校验。dry-run **不能证明 token 所有权、PWA 安装态或通知实际可达**。永久无效 token 会按原 token 条件清除，临时错误保留订阅并返回可重试错误。

IP 使用服务端请求上下文（依赖部署正确配置可信代理），不接受请求体提供的 IP；设备号按 UUID 归一化后仅保存 SHA-256 摘要。设备号保存在当前站点的浏览器本地存储中，退出账号不会清除；不同域名、浏览器或隔离存储环境不保证共享，清理站点数据后会重新生成。限制按相同设备号的摘要累计，不按物理手机累计。设备号可以被重置或伪造，因此只能辅助防刷，不能保证物理设备唯一。任一维度达到上限时返回明确错误，不发奖。

领取限额检查、领取记录、余额、打码任务和账本同事务；跨账号同 IP／设备的并发请求由 PostgreSQL 事务锁串行校验，不会超领。失败回滚不消耗名额。并发或重复请求不会再次入账。重复请求不重复显示“本次到账”，不重新触发领奖风控钩子。失败返回错误，不返回 `pending`，没有异步补发任务。

请求超时或服务端异常后，先查询详情：已领取则更新余额并隐藏卡片；确认未领取后允许用户重试；无法确认时只提供“查询领取结果”，不要直接再次发起领取。

## 客户端顺序 {#client}

1. 展示任务实际金额、打码倍数和对应规则。
2. 所有平台在非 PWA 环境点击领取时，提示先安装 PWA（添加到主屏幕）并从桌面打开，同时直接调用现有 PWA 安装流程，按浏览器能力展示系统安装窗口或安装引导；不申请通知、不提交订阅或领奖。此限制仅为客户端引导，不是后端可信安装资格证明；超时后的领取结果查询不受影响。在 PWA 中点击领取后，在原始手势调用链申请通知权限；已授权则直接获取 token。
3. 获取 SDK token → 上报订阅 → 主动领取。本期不要求游客绑定邮箱或手机。
4. `credited` 显示到账结果，`already_claimed` 显示已领取；刷新活动列表、余额和奖金记录。
5. 权限拒绝、环境不支持、订阅失败时明确提示恢复方式，不发起领取。订阅成功但领取失败不撤销订阅，后续由用户重试领取，不承诺自动补发。
6. 领取和结果查询在同账号下共享进行中状态；未成功时结束后冷却 3 秒，显示重试倒计时并禁止重复操作。重试期间保留上次提示，不清空卡片或重新加载活动列表。IP 或设备任一领取限额达到上限时，统一提示「通知奖励已达上限」，不向玩家区分命中的限制维度，不重复打开安装流程。
