订阅状态未同步问题排查
## 1. 问题背景
by 于成季
·
知与行
CloudBase
1. 问题背景
用户反馈:后台已开通会员,但 App 页面仍显示未订阅状态(免费用户界面)。
问题范围涉及两个云函数和一张关系库表:
apple-iap-verify:接收 App IAP 购买凭证,向 Apple API 验签,写入user_subscriptions表check-subscription:读取user_subscriptions表,返回用户的订阅 plan/statususer_subscriptions表:存储用户订阅记录
2. 问题列表
2.1 订阅写入成功,但查询仍返回免费
根因链:
- why1:
apple-iap-verify验签 premium 成功后向数据库写入记录,但页面仍显示免费状态 - why2: 数据库中该用户的 premium 记录不存在,
check-subscription只返回旧的 basic 记录 - why3:
apple-iap-verify的 INSERT 语句执行了,但数据库中无该记录 - why4: 真正根因——MySQL DATETIME 格式不兼容:
Error 1292 (22007): Incorrect datetime value: '2026-04-13T06:06:11.000Z' for column 'current_period_end'
证据:
- RDB 日志(关键):
INSERT INTO user_subscriptions ... VALUES ('2026-04-13T06:06:11.000Z'...) result: failed - MySQL 报错:ISO 8601 UTC 格式(
2026-04-13T06:06:11.000Z)不被 MySQL DATETIME 类型接受 - 云函数日志显示"插入新记录"成功,但实际数据库中无该记录
- 数据库实际只有旧记录:
basic, current_period_end=2026-03-31(此记录日期恰好能被 MySQL 接受)
短期方案:
修复 apple-iap-verify/index.js,将 Date 对象格式化为 MySQL DATETIME 格式 YYYY-MM-DD HH:MM:SS
长远方案:
- 将
aiQuota.js抽离为独立 npm 包,两函数通过包依赖引用,消除代码副本不一致风险 - 或在云函数 INSERT/UPDATE 后增加 SELECT 验证,确保写入成功
采纳方案:
- 修复代码:在
apple-iap-verify/index.js添加formatDateForMySQL()函数,将current_period_end和updated_at格式化为YYYY-MM-DD HH:MM:SS格式 - 重新部署
apple-iap-verify云函数 - 重新部署
check-subscription云函数(添加com.orange.endoftime.sub.vip.monthly_68_8→premium映射) - 手动写入 premium 记录修复历史数据
补充发现:check-subscription/aiQuota.js 缺少 com.orange.endoftime.sub.vip.monthly_68_8 → premium 的产品映射(已在本次部署中修复)
3. 小结
本次问题本质是数据写入失败被静默忽略——apple-iap-verify 日志显示"插入新记录"成功,但 RDB 层实际报错 Error 1292,导致 premium 记录未写入数据库,check-subscription 只能读到旧的 basic 记录。
修复要点:
- 根因:MySQL DATETIME 不接受 ISO 8601 UTC 格式
2026-04-13T06:06:11.000Z,需转为YYYY-MM-DD HH:MM:SS - 修复:
apple-iap-verify/index.js添加formatDateForMySQL()格式化函数 - 验证:修复后
check-subscription正确返回plan: premium, ai_enabled: true, daily_token_limit: 200000