2026-04-01 打卡

1. 今天学了什么 / 做了哪些具体操作

Webhook 签名验证(核心安全工作)

背景: 昨天 Lemon Squeezy 支付集成上线,但 webhook 没有签名验证,任何人都能伪造支付成功通知来给自己加字数。

实现过程:

  1. 第一版签名验证(错误): 参考了 Stripe 的格式,以为 LS 也是 timestamp=xxx,hmac_sha256=yyy 的复合格式。部署后全部 401 失败。

  2. 查文档修正: 从 LS 官方文档确认实际格式——LS 的签名非常简单,就是纯 HMAC-SHA256(signing_secret, raw_body) 的 hex digest,没有 timestamp,没有拼接。X-Signature header 直接就是 64 位十六进制字符串。

  3. 调试过程: CF Pages 的 wrangler tail 无法使用(打包后的 _worker.js 不被识别为 Pages Function),本地也没有好的方式看线上日志。最终方案:在 webhook 响应里临时返回 debug 信息(签名比对结果),在 LS Dashboard 的 webhook 记录里查看。

  4. 验证结果: match: true,签名完全正确。

最终实现(Edge Runtime 兼容):

  • 使用 Web Crypto API(crypto.subtle.importKey + crypto.subtle.sign),不用 Node.js crypto
  • 常量时间比较防时序攻击
  • 环境变量 LEMONSQUEEZY_WEBHOOK_SECRET 配置签名密钥

清理 PayPal 残留代码

删除了 4 个文件,全局搜索确认零残留引用:

  • lib/paypal.ts
  • app/api/paypal/create-order/route.ts
  • app/api/paypal/capture-order/route.ts
  • app/api/paypal/config/route.ts
  • components/ui/paypal-button.tsx

修复 JWT 不刷新 plan 的 bug

问题: 用户买了 Pro 套餐后,pricing 页面仍然能买 Basic。根本原因是 NextAuth 的 JWT callback 只在首次登录时查 DB 拿 plan,后续 session 续期都用缓存的旧值。

修复: 把 DB 查询从 if (user) 块里移出来,改为每次 JWT callback 都查 DB。这样 webhook 更新 plan 后,下次 session 刷新就能拿到最新值。

附带修复:

  • 注册默认字数从 30 改成 3000(和页面文案"3,000 free words"一致)
  • session callback 里把 usage_count 换成 credits(字数)

CF Pages 日志排查踩坑

  • wrangler pages deployment tail 报错 "does not have a Pages Function"
  • 原因:@cloudflare/next-on-pages 把所有路由打包成单个 _worker.js,wrangler 不认为有 Functions
  • CF API 也没有日志拉取接口
  • 最终用"响应里返回 debug info"的方式绕过

2. 解决了什么问题 / 有什么新认识 / 哪些地方需要改进

解决的问题:

  1. Webhook 安全漏洞 → HMAC-SHA256 签名验证,防止伪造支付通知
  2. PayPal 残留代码 → 全部清理干净
  3. 购买后套餐状态不同步 → JWT 每次 callback 都查 DB

新认识:

  1. 不同支付平台的签名格式完全不同——Stripe 用 timestamp+hmac,Lemon Squeezy 用纯 hmac。不能想当然地照搬。
  2. CF Pages 的日志能力很弱——没有实时 tail,没有历史日志 API。对于调试 webhook 这种场景,只能在响应里返回 debug 信息。
  3. Edge Runtime 的 crypto API 和 Node.js 不同——不能用 require('crypto'),必须用 crypto.subtle
  4. NextAuth JWT callback 的触发时机——不只是首次登录,每次 session 刷新都会触发。所以把 DB 查询放外面不会增加太多开销,但能保证数据实时性。

需要改进:

  1. 部署后应该有个自动化冒烟测试(curl webhook endpoint),不能每次都靠手动测试
  2. CF Pages 的日志问题需要找个长期方案,比如接入 Sentry 或 Loki

3. 收获

  • 完整实现了 HMAC-SHA256 签名验证(Edge Runtime 版本)
  • 学会了在 CF Pages 无法看日志时的调试技巧
  • 加深了对 NextAuth JWT callback 生命周期的理解
  • 养成了"先查官方文档再写代码"的习惯(签名格式踩坑的教训)

4. 接下来要学什么 / 做什么(可执行的规划)

  • [ ] 切 Live Mode:生成 Live API Key,publish 3 个产品,替换代码里的 Test API Key
  • [ ] SEO 优化:生成 sitemap.xml,补全各页面 meta tags(title/description/OG/Twitter Card)
  • [ ] Product Hunt 发布准备:准备文案、截图、tagline
  • [ ] 监控方案:接入 Sentry 或类似工具,解决 CF Pages 日志问题