Skip to content

Commit 1398ed7

Browse files
committed
feat(payment): tiered recharge bonus with idempotent fulfillment and refund clawback
- Admin-configurable bonus tiers (e.g. recharge 1000 get 100) under payment settings, stored as RECHARGE_BONUS_ENABLED/TIERS; highest matching tier wins, judged on credited balance, balance orders only - Orders snapshot bonus_amount at creation (migration 948); fulfillment grants the bonus via the "{recharge_code}-B" anchor code (type recharge_bonus), retry-safe, ledger notes payment_bonus:{code} - Bonus credits balance without touching total_recharged; visible in user redeem history and admin balance history - Full refunds claw back the bonus to close the recharge-refund arbitrage loop; partial refunds keep it - User checkout page shows tier badges, per-amount bonus and credited total; result page and order table mark bonus orders
1 parent 092fbf8 commit 1398ed7

58 files changed

Lines changed: 1437 additions & 53 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.spec/knowledge/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ metadata:
2323
| 文档 | 一句话 |
2424
|------|--------|
2525
| [`features/payment.md`](features/payment.md) | 内置支付系统:服务商接入、Webhook、订单状态、可信余额履约限流边界、管理端集成 Admin API |
26+
| [`features/recharge-bonus.md`](features/recharge-bonus.md) | 充值满赠:档位配置、订单快照、-B 锚定码幂等发放赠送余额并流水溯源、全额退款回收赠送 |
2627
| [`features/subscription-pricing.md`](features/subscription-pricing.md) | 订阅套餐计价口径与计算器工具——定价 / 折扣 / 日周上限怎么算 |
2728
| [`features/subscription-credit-pool.md`](features/subscription-credit-pool.md) | 用户级订阅额度池:多订阅按时间扣费、额度优先消费、不足拆分扣余额、ledger 审计;用户自助重置周限(每订阅周期一次) |
2829
| [`features/subscription-admin.md`](features/subscription-admin.md) | issue #47 订阅管理三项修复:撤销不计浪费、限额刷新时间可配、套餐入口迁移 |
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
name: recharge-bonus
3+
description: 充值满赠:管理员配置满赠档位(如充 1000 送 100)、订单快照、履约幂等发放赠送余额并流水溯源原订单、全额退款回收赠送——配置或排查赠送发放/流水时查这篇
4+
metadata:
5+
type: doc
6+
level: L2
7+
status: 已交付
8+
---
9+
10+
# 充值满赠(Recharge Bonus)
11+
12+
简介:管理员在支付设置配置满赠档位,用户余额充值支付成功履约时自动发放一笔赠送余额;赠送以独立兑换码流水记账并可溯源到原充值订单。
13+
14+
## 背景 / 目标
15+
16+
- 运营需要「充值 1000 送 100」类活动能力;上游 sub2api 只有余额倍率(multiplier),无档位满赠。
17+
- 赠送必须:前端可见、支付成功后自动发放、流水可追溯到源充值订单、重试不重复发放、退款不可套利。
18+
19+
## 设计
20+
21+
### 配置
22+
23+
- 设置键:`RECHARGE_BONUS_ENABLED`("true"/"false")+ `RECHARGE_BONUS_TIERS`(JSON 数组 `[{"threshold":1000,"bonus":100},...]`,settings KV)。
24+
- 管理端走 `PUT /api/v1/admin/settings` 的 `payment_recharge_bonus_enabled` / `payment_recharge_bonus_tiers`(也支持 `PUT /admin/payment/config`);校验:threshold/bonus > 0、≤2 位小数、threshold 唯一、≤20 档。
25+
- 读取统一经 `GetPaymentConfig` → `parseRechargeBonusTiers`(空/坏 JSON → 空 slice)→ `normalizeRechargeBonusTiers`(剔非法、同 threshold 留首个、**按 threshold 降序**)。不经过 `parseSettings`(payment 段直接从 PaymentConfigService 取)。
26+
27+
### 判定规则(已决策)
28+
29+
- **单档满赠、取满足条件的最高档**(不做「每满 X 送 Y」叠加):`computeRechargeBonusAmount`。
30+
- 判定基准 = **到账余额**(乘倍率后的 `order.Amount`;倍率 1 时即用户输入金额)。
31+
- 仅 `order_type = balance` 的外部支付订单参与;余额购订阅不参与。
32+
33+
### 订单快照
34+
35+
- `payment_orders.bonus_amount`(decimal(20,2) DEFAULT 0,迁移 **948**):`createOrderInTx` 创建时计算快照;履约与展示均用快照,改配置不影响已创建订单(与订阅快照同模式)。
36+
37+
### 履约幂等
38+
39+
- `doBalance`:主码 settle/redeem 后、两分支汇合处调 `fulfillRechargeBonus`(主码已用但赠送上次失败的重试可补发)→ affiliate 返利 → markCompleted(audit 加 `bonusAmount`)。
40+
- 赠送锚定码 = `{recharge_code}-B`,type=`recharge_bonus`、notes=`payment_bonus:{recharge_code}`;与主码同构走 `resolveRedeemAction`(Create/Redeem/SkipCompleted),失败进 markFailed → 既有退避重试链路,天然不重复加钱。
41+
42+
### 入账与流水
43+
44+
- `redeem()` 新 case `recharge_bonus` → `userRepo.UpdateBalanceNoStat`:**只加 balance,不计 total_recharged**(不污染累计充值统计/签到门槛)。
45+
- 流水即 `redeem_codes` 一条 status=used 记录:用户侧 `GET /redeem/history` 与管理端用户余额历史天然可见(notes 暴露条件、`SumPositiveBalanceByUser` 白名单均已加 `recharge_bonus`)。
46+
47+
### 退款回收(防套利)
48+
49+
- 用户 `RequestRefund` 余额校验改为 `amount + bonus_amount`;`prepDeduct` **全额退款**(refund >= amount)时 `BalanceToDeduct = min(refund + bonus, balance)`,部分退款不回收。否则「充值→退款」循环可白嫖赠送额。
50+
51+
### 前端
52+
53+
- 用户侧 `GET /payment/checkout-info` 新增 `recharge_bonus_enabled` / `recharge_bonus_tiers`(降序);充值页金额卡顶部满赠 badge 行 + 到账预览「+ 赠送」badge 与「到账合计」行;结果页/订单表(OrderTable 组件)`bonus_amount > 0` 显示赠送标记。
54+
- 管理端 SettingsView 支付设置「充值满赠」块:Toggle + 档位行编辑器(≤20 档,空行保存前剔除);i18n 中/英/繁已补。
55+
56+
## 已决策
57+
58+
- 最高档命中而非叠加:最常见运营口径,实现与文案都最简单。
59+
- 按到账余额判定:与订单记录、前端「到账」展示一致(倍率站点语义清晰)。
60+
- 赠送走兑换码锚定(而非直接改余额):复用履约幂等三件套(lease、锚定码、CAS)与流水可见性,零新表。
61+
- 快照到订单:配置变更不影响进行中订单,可预测、可审计。
62+
- 全额退款回收赠送:堵套利;部分退款不回收(口径简单、对用户友好)。
63+
64+
## 待解决
65+
66+
- 部分退款按比例回收赠送(当前不做,如运营需要再议)。
67+
68+
## 相关
69+
70+
- [[payment]](履约链路)、[[daily-checkin]](兑换码流水记账同模式)、[[affiliate-signup-bonus]](余额历史框架)
71+
- 后端:`payment_config_service.go`、`payment_order.go`(快照)、`payment_fulfillment.go`(`fulfillRechargeBonus`)、`redeem_service.go`、`payment_refund.go`、迁移 `948_recharge_bonus.sql`、测试 `recharge_bonus_test.go`
72+
- 前端:`PaymentView.vue`、`PaymentResultView.vue`、`OrderTable.vue`、`SettingsView.vue`、`RedeemView.vue`、`UserBalanceHistoryModal.vue`

‎backend/ent/migrate/schema.go‎

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎backend/ent/mutation.go‎

Lines changed: 88 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎backend/ent/paymentorder.go‎

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎backend/ent/paymentorder/paymentorder.go‎

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)