优惠券功能允许用户在充值时获得额外的金币奖励。系统会根据配置的策略自动向符合条件的用户发放优惠券。
架构: 采用事件驱动模式 —— 订单支付成功后触发 OrderPaid 事件,由 ProcessCouponOnOrderPaid Listener 异步处理优惠券逻辑(验证、发放金币、标记已用),与订单核心流程完全解耦。
路由位置: routes/game.php(checkGameLogin + mustGameLogin 中间件组)
| 类型 | type_id | 说明 |
|---|---|---|
| 固定金额 | 1 | 充值后赠送固定金额金币(与充值额无关) |
| 充值百分比 | 2 | 充值后按充值金额的百分比赠送金币,有上限 |
| 状态 | status | 说明 |
|---|---|---|
| 未使用 | 0 | 可用 |
| 已使用 | 1 | 已绑定订单并发放金币 |
| 已过期 | 2 | 超过有效期 |
获取用户当前可用的优惠券列表,同时触发自动发放逻辑。系统会根据 config/coupon.php 中的规则检查用户是否符合发放条件,符合则自动发放新券。
GET /game/coupon/list
鉴权: checkGameLogin + mustGameLogin 中间件(从 $request->globalUser->UserID 获取用户ID)
请求示例:
GET /game/coupon/list
成功响应:
{
"code": 200,
"msg": "success",
"data": {
"list": [
{
"id": 1,
"name": "new_user_bonus",
"type": "percent",
"type_id": 2,
"value": 50,
"min_recharge": 10,
"max_bonus": 50,
"desc": "+50% bonus (max 50)",
"status": 0,
"expire_at": "2026-06-03 12:00:00",
"issued_at": "2026-05-27 12:00:00"
}
],
"new_issued": [
{
"id": 1,
"name": "new_user_bonus",
"type": "percent",
"type_id": 2,
"value": 50,
"min_recharge": 10,
"max_bonus": 50,
"desc": "+50% bonus (max 50)",
"status": 0,
"expire_at": "2026-06-03 12:00:00",
"issued_at": "2026-05-27 12:00:00"
}
]
}
}
响应字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| list | array | 当前所有可用优惠券 |
| new_issued | array | 本次请求新发放的优惠券(首次调用时可能非空) |
| id | int | 优惠券ID,充值下单时传入 |
| name | string | 优惠券名称(标识) |
| type | string | fixed=固定金额, percent=百分比 |
| type_id | int | 1=固定金额, 2=百分比 |
| value | float | 优惠值(元 or %) |
| min_recharge | float | 最低充值门槛(元) |
| max_bonus | float | 最大赠送上限(元),百分比券有效 |
| desc | string | 前端展示文案 |
| status | int | 0=可用, 1=已用, 2=过期 |
| expire_at | string | 过期时间 |
| issued_at | string | 发放时间 |
在用户选择优惠券后、发起支付前调用,用于向用户展示预计获得的额外金币。
POST /game/coupon/preview
鉴权: checkGameLogin + mustGameLogin 中间件
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userID | int | 是 | 用户ID |
| coupon_id | int | 是 | 优惠券ID |
| payAmt | float | 是 | 充值金额(元) |
请求示例:
POST /game/coupon/preview
Content-Type: application/json
{
"userID": 123456,
"coupon_id": 1,
"payAmt": 50
}
成功响应:
{
"code": 200,
"msg": "success",
"data": {
"coupon_id": 1,
"bonus_amount": 25.00,
"bonus_coins": 2500,
"total_amount": 75.00
}
}
响应字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| coupon_id | int | 优惠券ID |
| bonus_amount | float | 预计赠送金额(元) |
| bonus_coins | int | 预计赠送金币(分,1元=100分) |
| total_amount | float | 预计到账总额(元)= 充值额 + 赠送额 |
失败响应:
{
"code": 301,
"msg": "Minimum recharge 10 required for this coupon",
"data": []
}
在支付接口中新增 coupon_id 参数,传入要使用的优惠券ID。
影响接口: POST /payment_entry/pay(routes/game.php)
新增参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| coupon_id | int | 否 | 0 | 优惠券ID,0=不使用优惠券 |
请求示例:
{
"payAmt": 50,
"pay_type": 1,
"pay_method": "2",
"GiftsID": 0,
"coupon_id": 1
}
处理流程(事件驱动):
ProcessCouponOnOrderPaid Listener 异步处理:
系统在用户调用 GET /game/coupon/list 时自动检查以下条件并发放优惠券。
配置文件: config/coupon.php
| 策略名称 | 条件 | 券类型 | 优惠 | 最低充值 | 有效期 |
|---|---|---|---|---|---|
| new_user_bonus | 注册≤3天 + 从未充值 | 百分比 | 50% (上限50元) | 10元 | 7天 |
| comeback_bonus | 最近充值≥14天前 | 百分比 | 30% (上限30元) | 20元 | 3天 |
| vip_recharge_bonus | 累计充值≥500元 | 固定 | 20元 | 50元 | 14天 |
| 条件类型 | 参数 | 说明 |
|---|---|---|
| new_user | days | 注册天数 ≤ days 且从未充值 |
| inactive_days | days | 最近一次充值距今 ≥ days(需有充值历史) |
| recharge_total | amount | 累计充值金额 ≥ amount(元) |
| vip_level | level | VIP等级 ≥ level |
同一用户不会重复获得同名的有效优惠券。只有当前券过期或使用后,下次请求才会重新发放。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT IDENTITY PK | 主键 |
| user_id | INT NOT NULL | 用户ID |
| coupon_name | VARCHAR(50) | 优惠券名称 |
| coupon_type | TINYINT | 1=固定金额, 2=百分比 |
| coupon_value | DECIMAL(10,2) | 优惠值 |
| min_recharge | DECIMAL(10,2) | 最低充值门槛 |
| max_bonus | DECIMAL(10,2) | 最大赠送上限 |
| bonus_coins | INT | 实际赠送金币(分) |
| order_sn | VARCHAR(64) | 关联订单号 |
| status | TINYINT | 0=未使用, 1=已使用, 2=已过期 |
| issued_at | DATETIME | 发放时间 |
| used_at | DATETIME | 使用时间 |
| expire_at | DATETIME | 过期时间 |
优惠券通过 order_sn 字段关联订单,不在 order 表中新增字段,保持订单表结构不变。
coupon_id 在支付请求→回调之间通过 Redis 临时存储(key=coupon_{order_sn},TTL=24h)。
使用优惠券后,金币增加记录写入 QPRecordDB.dbo.RecordUserScoreChange:
| 字段 | 值 | 说明 |
|---|---|---|
| Reason | 55 | 优惠券赠送(与现有21/33/44/45/49/51/52/72/73不冲突) |
可通过 config/coupon.php 中的 score_reason 配置修改。