订阅状态未同步问题排查

## 1. 问题背景

by 于成季 ·
知与行 CloudBase

1. 问题背景

用户反馈:后台已开通会员,但 App 页面仍显示未订阅状态(免费用户界面)。

问题范围涉及两个云函数和一张关系库表:

  • apple-iap-verify:接收 App IAP 购买凭证,向 Apple API 验签,写入 user_subscriptions
  • check-subscription:读取 user_subscriptions 表,返回用户的订阅 plan/status
  • user_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 验证,确保写入成功

采纳方案

  1. 修复代码:在 apple-iap-verify/index.js 添加 formatDateForMySQL() 函数,将 current_period_endupdated_at 格式化为 YYYY-MM-DD HH:MM:SS 格式
  2. 重新部署 apple-iap-verify 云函数
  3. 重新部署 check-subscription 云函数(添加 com.orange.endoftime.sub.vip.monthly_68_8premium 映射)
  4. 手动写入 premium 记录修复历史数据

补充发现check-subscription/aiQuota.js 缺少 com.orange.endoftime.sub.vip.monthly_68_8premium 的产品映射(已在本次部署中修复)


3. 小结

本次问题本质是数据写入失败被静默忽略——apple-iap-verify 日志显示"插入新记录"成功,但 RDB 层实际报错 Error 1292,导致 premium 记录未写入数据库,check-subscription 只能读到旧的 basic 记录。

修复要点:

  1. 根因:MySQL DATETIME 不接受 ISO 8601 UTC 格式 2026-04-13T06:06:11.000Z,需转为 YYYY-MM-DD HH:MM:SS
  2. 修复apple-iap-verify/index.js 添加 formatDateForMySQL() 格式化函数
  3. 验证:修复后 check-subscription 正确返回 plan: premium, ai_enabled: true, daily_token_limit: 200000
← 所有文章