fix(billing): backfill finite image quota after free-plan rebuild

Expired paid users were rebuilt as free with period_images_limit=0, which the quota system treats as unlimited. Write the catalog quota on rebuild and repair existing finite plans on schema ensure.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-14 21:39:55 +08:00
parent 41f245c88a
commit 8cf1c772a7
4 changed files with 113 additions and 3 deletions
@@ -0,0 +1,43 @@
# 2026-08-14 过期后补建免费套餐把图片额度写成无限
## 现象
生产管理后台用户详情里,部分普通用户的图片额度显示「无限」,输入框和「保存额度」被禁用。典型用户:柯彤(`wx_1ugymxba`),当时有效套餐为免费版,备注「系统迁移补建免费套餐」,`period_images_limit = 0`,已用 4 张。
后台把 `period_images_limit === 0` 视为无限,用户详情和 `setImageQuota` 都拒绝调整。这些用户可以无限制调用 `image_make`
## 根因
1. `initSchema` / `migrateSchema` 在用户没有有效订阅时会补建免费套餐,INSERT 只写了 token 额度,没有写 `period_images_limit`
2. 该列 `DEFAULT 0`,而计费把 `0` 设计成「无限」,不是「没有额度」。
3. 付费套餐过期后,Portal / Plaza 下次启动会给该用户补建免费套餐,于是变成无限生图。
生产套餐目录里免费版是 10 张/周期,不是无限。事故发生时有 33 条有效免费订阅命中该补建备注且上限为 0;另有 86 条正常免费订阅上限为 10。
## 修复
- 补建免费套餐时写入套餐目录的 `period_images`(无目录时回退 10)。
- `ensurePlanCatalogSchema` 启动时把「有效订阅上限为 0,但目录额度 > 0」的记录回填为目录值,并记一条 `plan_change` 流水。真正无限套餐(目录 `period_images = 0`)不改。
- 生产已对这 33 条有效订阅做一次数据回填。代码修复需随 Memind 发布后才会阻止再次补建漏写。
## 排查入口
```sql
SELECT u.username, u.display_name, s.plan_type, s.note,
s.period_images_limit, s.period_images_used, s.period_images_bonus
FROM h5_subscriptions s
JOIN h5_users u ON u.id = s.user_id
WHERE s.status = 'active'
AND s.expires_at > UNIX_TIMESTAMP() * 1000
AND s.period_images_limit = 0;
SELECT plan_type, name, period_images FROM h5_plan_catalog;
```
管理后台:用户详情「图片生成额度」显示无限且无法保存,同时计费里该用户是免费套餐、备注含「系统迁移补建」。
## 影响边界与回滚
- 目录里本身就是无限的套餐(`period_images = 0`)不会被回填。
- 已用量超过目录额度的用户回填后剩余为 0,不能再继续按无限额度生图。
- 回滚代码不会自动把已回填的上限改回 0;如需恢复无限,要显式改订阅或目录。