# bet-order-service 注单服务

## 服务职责

bet-order-service 负责注单主状态机、创建/结算幂等、反奖 payout 固化，以及后续钱包、回调、数仓/RTP 采集事件字段的预留。第一阶段实现为可本地运行的 Hertz HTTP 服务，使用内存仓储与内存事件 Producer，接口和领域模型按后续 PostgreSQL + Redpanda 落地方式设计。

## 本地命令

```bash
cd /Users/amumu/Desktop/beifen/golang新架构/平台服务/bet-order-service
go test ./...
go build ./cmd/bet-order-service
PORT=8080 go run ./cmd/bet-order-service
```

健康检查：

```bash
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/ready
```

## HTTP 接口

### 创建注单

```http
POST /bet-orders
Content-Type: application/json
```

请求字段：

| 字段 | 说明 |
| --- | --- |
| event_id | 上游 order.ingested 或业务接入事件 ID，可空但建议传入 |
| trace_id | 全链路 trace_id，可空，服务会回填 |
| merchant_id | 商户 ID，必填 |
| player_id | 玩家 ID，必填 |
| game_code | 游戏编码，必填 |
| round_id | 局号，必填 |
| bet_order_id | 注单 ID，必填，作为幂等核心字段 |
| idempotency_key | 创建幂等键；为空时使用 `merchant_id:player_id:round_id:bet_order_id` |
| bet_amount | 投注金额，最小货币单位，必须大于 0 |
| currency | 币种，必填 |
| config_version | 游戏/商户配置版本，便于审计 |

创建成功后状态为 `CREATED`，服务会预留并发布 `wallet.debit.requested` 事件字段；重复创建返回同一注单视图并带 `code=IDEMPOTENT_HIT`。

### 结算注单

```http
POST /bet-orders/{id}/settle
Content-Type: application/json
```

请求字段：

| 字段 | 说明 |
| --- | --- |
| event_id | 游戏结果或结算事件 ID；为空时服务生成 `bet_settled_*` |
| trace_id | 全链路 trace_id |
| idempotency_key | 结算幂等键，必填 |
| payout | 反奖金额，最小货币单位，允许为 0 |
| result_hash | 游戏结果哈希，必填，结算后不可变 |
| rtp_bucket | RTP 分桶，如 `demo-slots:base` |

状态机要求当前注单必须已进入 `DEBITED`。第一阶段 HTTP 暴露创建、结算、查询三类接口；`CREATED -> DEBITED` 由服务层 `MarkDebited` 承接未来 `wallet.debit.succeeded` 事件消费者。重复结算使用相同 `idempotency_key` 时返回同一结果，不重复发布 `bet.settled`。

### 查询注单

```http
GET /bet-orders/{id}
```

返回注单当前状态、投注金额、payout、幂等键、trace_id、钱包/结算/回调/数仓事件字段。

## 状态机

第一阶段核心状态：

```text
CREATED -> DEBITED -> SETTLED
CREATED -> FAILED
DEBITED -> FAILED
```

约束：

- `SETTLED` 后不可再次修改 payout/result_hash。
- `FAILED` 后不可结算。
- `SETTLED` 使用相同结算幂等键重复请求时返回幂等命中。
- 使用不同结算幂等键重复结算返回冲突。

## 并发与幂等设计

- 创建幂等键默认是 `merchant_id + ":" + player_id + ":" + round_id + ":" + bet_order_id`，覆盖高并发下注、客户端重试和网关重放。
- 内存实现用互斥锁保护 repository 读写；生产落库时应改为唯一索引 + 事务更新：
  - `UNIQUE (merchant_id, player_id, round_id, bet_order_id)`
  - `UNIQUE (create_idempotency_key)`
  - `UNIQUE (bet_order_id, settle_idempotency_key)`，其中 `settle_idempotency_key` 非空时生效
- 状态流转应使用乐观锁或条件更新，例如 `UPDATE ... WHERE bet_order_id=? AND status='DEBITED'`，避免并发重复结算。
- 事件发布字段已预留 `wallet_debit_requested_id`、`wallet_debit_succeeded_id`、`settlement_event_id`、`merchant_callback_event_id`、`warehouse_event_id`，便于后续 outbox/Redpanda 一致性改造。

## 数据库表建议

建议在 `order_db` 建 `bet_orders`：

| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| id | bigserial / bigint | 内部主键 |
| merchant_id | varchar(64) | 商户 ID |
| player_id | varchar(64) | 玩家 ID |
| game_code | varchar(64) | 游戏编码 |
| round_id | varchar(128) | 局号 |
| bet_order_id | varchar(128) | 注单 ID |
| create_idempotency_key | varchar(256) | 创建幂等键 |
| settle_idempotency_key | varchar(256) | 结算幂等键 |
| bet_amount | bigint | 投注金额 |
| payout | bigint | 反奖金额 |
| currency | varchar(16) | 币种 |
| status | varchar(32) | CREATED/DEBITED/SETTLED/FAILED |
| result_hash | varchar(256) | 游戏结果哈希 |
| trace_id | varchar(128) | 链路追踪 |
| source_event_id | varchar(128) | 来源事件 |
| wallet_debit_requested_id | varchar(128) | 钱包扣款请求事件 |
| wallet_debit_succeeded_id | varchar(128) | 钱包扣款成功事件 |
| settlement_event_id | varchar(128) | bet.settled 事件 |
| merchant_callback_event_id | varchar(128) | 商户回调事件 |
| warehouse_event_id | varchar(128) | 数仓写入事件 |
| rtp_bucket | varchar(128) | RTP 分桶 |
| rtp_sample_key | varchar(128) | RTP 聚合键，默认 `merchant_id:game_code` |
| failure_reason | varchar(512) | 失败原因 |
| created_at / updated_at / settled_at | timestamptz | 时间字段 |

## 反奖链路与 RTP 采集衔接

结算时服务把 `payout`、`bet_amount`、`currency`、`result_hash`、`rtp_bucket`、`rtp_sample_key` 写入注单视图，并发布 `bet.settled` 事件。后续链路建议：

1. settlement-service 或 game result consumer 调用/驱动 `Settle`，固化 payout。
2. ledger/wallet 侧按 payout 生成 credit 流水，保持资金账本与注单状态分离。
3. callback-service 读取 `bet.settled` 后通知商户，回写 `merchant_callback_event_id`。
4. warehouse-writer 读取同一事件写入 ClickHouse 明细。
5. risk-rtp-service 按 `rtp_sample_key`、`rtp_bucket` 聚合投注额与 payout，计算商户/游戏/配置版本维度 RTP。

## OpenAPI 与配置

- OpenAPI：`平台服务/bet-order-service/api/openapi/bet-order-service.openapi.yaml`
- 配置样例：`平台服务/bet-order-service/configs/config.example.yaml`
