# POST /api/v1/coupon/session — 领券任务流水上报

> 所属:Coupon 组（前缀 `/api/v1/coupon`） | 鉴权:无（按 device_id/trace_id 区分） | [← 返回 API 索引](../README.md)

客户端两段上报一次领券流水（发起 / 收尾），按 `trace_id` upsert 到 `coupon_session`。供 admin「领券数据」看板算发起/完成数、耗时分位、机型维度。

## 入参

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `trace_id` | string | ✅ | 领券 trace 标识（同领券循环 step 的 trace_id） |
| `device_id` | string | ✅ | 设备 ID（与领券循环一致） |
| `status` | string | ✅ | `started` / `completed` / `failed` / `abandoned` |
| `started_at_ms` | int | ✅ | 发起墙钟毫秒（客户端 `System.currentTimeMillis`） |
| `user_id` | int \| null | ❌ | 登录用户 ID（未登录可空） |
| `platforms` | list[string] \| null | ❌ | 勾选的平台列表 |
| `origin_package` | string \| null | ❌ | 发起领券的 App 包名 |
| `device_model` | string \| null | ❌ | 机型（如 `24115RA8EC`） |
| `rom` | string \| null | ❌ | ROM 信息（如 `MIUI 14.0.8`） |
| `app_env` | string \| null | ❌ | 应用环境（`prod` / `test`） |
| `elapsed_ms` | int \| null | ❌ | 全程耗时（ms），收尾帧必带 |
| `platform_elapsed` | dict \| null | ❌ | 各平台耗时（如 `{"meituan": 3200, "jd": 2800}`），收尾帧带 |
| `claimed_count` | int \| null | ❌ | 本场实际领到的券张数 |
| `trace_url` | string \| null | ❌ | trace 云端 URL（done 帧带） |

Mock 入参（started）:
```json
{
  "trace_id": "tr_20260703_a1b2c3d4",
  "device_id": "android_abc123def456",
  "status": "started",
  "started_at_ms": 1719993600000,
  "user_id": 42,
  "platforms": ["meituan", "jd"],
  "origin_package": "com.sankuai.meituan",
  "device_model": "24115RA8EC",
  "rom": "MIUI 14.0.8",
  "app_env": "prod"
}
```

Mock 入参（completed）:
```json
{
  "trace_id": "tr_20260703_a1b2c3d4",
  "device_id": "android_abc123def456",
  "status": "completed",
  "started_at_ms": 1719993600000,
  "user_id": 42,
  "platforms": ["meituan", "jd"],
  "origin_package": "com.sankuai.meituan",
  "device_model": "24115RA8EC",
  "rom": "MIUI 14.0.8",
  "app_env": "prod",
  "elapsed_ms": 12500,
  "platform_elapsed": {"meituan": 5200, "jd": 4800},
  "claimed_count": 3,
  "trace_url": "https://trace.shaguabijia.com/tr_20260703_a1b2c3d4"
}
```

## 出参

响应 `200`:
```json
{"ok": true}
```

## 错误码
- `422` 必填字段缺失或类型不符

## 说明
- 不鉴权（同领券循环 MVP，按 `device_id`/`trace_id`）
- 写库失败不连累客户端（fire-and-forget，吞掉返回 ok）
- 一次领券两段上报：发起（started）→ 收尾（completed/failed/abandoned）
