payment-kit 1.27.2 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/__blocklet__.js +37 -0
  2. package/api/ocap-1.30-subpath-shims.d.ts +35 -0
  3. package/api/src/crons/index.ts +10 -0
  4. package/api/src/crons/metering-subscription-detection.ts +12 -14
  5. package/api/src/crons/overdue-detection.ts +51 -74
  6. package/api/src/integrations/arcblock/nft.ts +6 -2
  7. package/api/src/integrations/arcblock/stake.ts +3 -2
  8. package/api/src/integrations/arcblock/token.ts +4 -4
  9. package/api/src/integrations/blocklet/notification.ts +1 -1
  10. package/api/src/integrations/ethereum/tx.ts +29 -0
  11. package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
  12. package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
  13. package/api/src/integrations/stripe/resource.ts +8 -0
  14. package/api/src/libs/audit.ts +32 -16
  15. package/api/src/libs/auth.ts +49 -2
  16. package/api/src/libs/chain-error.ts +31 -0
  17. package/api/src/libs/error.ts +15 -0
  18. package/api/src/libs/event.ts +42 -1
  19. package/api/src/libs/invoice.ts +69 -34
  20. package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
  21. package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
  22. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
  23. package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
  24. package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
  25. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
  26. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
  27. package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
  28. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
  29. package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
  30. package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
  31. package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
  32. package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
  33. package/api/src/libs/pagination.ts +14 -9
  34. package/api/src/libs/payment.ts +25 -10
  35. package/api/src/libs/session.ts +1 -1
  36. package/api/src/libs/timing.ts +35 -0
  37. package/api/src/libs/util.ts +16 -15
  38. package/api/src/libs/wallet-migration.ts +72 -53
  39. package/api/src/queues/auto-recharge.ts +1 -1
  40. package/api/src/queues/credit-consume.ts +94 -12
  41. package/api/src/queues/credit-grant.ts +4 -0
  42. package/api/src/queues/event.ts +14 -2
  43. package/api/src/queues/invoice.ts +1 -0
  44. package/api/src/queues/payment.ts +83 -15
  45. package/api/src/queues/refund.ts +84 -71
  46. package/api/src/queues/subscription.ts +1 -0
  47. package/api/src/routes/checkout-sessions.ts +82 -43
  48. package/api/src/routes/connect/change-payment.ts +2 -0
  49. package/api/src/routes/connect/change-plan.ts +2 -0
  50. package/api/src/routes/connect/pay.ts +12 -3
  51. package/api/src/routes/connect/setup.ts +3 -1
  52. package/api/src/routes/connect/shared.ts +52 -39
  53. package/api/src/routes/connect/subscribe.ts +4 -1
  54. package/api/src/routes/credit-grants.ts +25 -17
  55. package/api/src/routes/donations.ts +2 -2
  56. package/api/src/routes/meter-events.ts +16 -6
  57. package/api/src/routes/payment-links.ts +1 -1
  58. package/api/src/routes/payment-methods.ts +1 -1
  59. package/api/src/routes/settings.ts +1 -1
  60. package/api/src/routes/tax-rates.ts +1 -1
  61. package/api/src/store/models/customer.ts +23 -1
  62. package/api/src/store/models/payment-method.ts +4 -0
  63. package/api/src/store/models/price.ts +23 -14
  64. package/api/tests/libs/wallet-migration.spec.ts +4 -4
  65. package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
  66. package/api/tests/queues/credit-consume.spec.ts +8 -4
  67. package/api/tests/routes/credit-grants.spec.ts +1 -0
  68. package/blocklet.yml +1 -1
  69. package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
  70. package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
  71. package/cloudflare/README.md +499 -0
  72. package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
  73. package/cloudflare/build.ts +151 -0
  74. package/cloudflare/did-connect-auth.ts +527 -0
  75. package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
  76. package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
  77. package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
  78. package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
  79. package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
  80. package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
  81. package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
  82. package/cloudflare/frontend-shims/js-sdk.ts +43 -0
  83. package/cloudflare/frontend-shims/mime-types.ts +46 -0
  84. package/cloudflare/frontend-shims/session.ts +24 -0
  85. package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
  86. package/cloudflare/index.html +40 -0
  87. package/cloudflare/migrate-to-d1.js +252 -0
  88. package/cloudflare/migrations/0001_initial_schema.sql +82 -0
  89. package/cloudflare/migrations/0002_indexes.sql +75 -0
  90. package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
  91. package/cloudflare/run-build.js +390 -0
  92. package/cloudflare/scripts/test-decrypt.js +102 -0
  93. package/cloudflare/shims/arcblock-ws.ts +20 -0
  94. package/cloudflare/shims/axios-http-adapter.ts +4 -0
  95. package/cloudflare/shims/axios-lite.ts +117 -0
  96. package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
  97. package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
  98. package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
  99. package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
  100. package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
  101. package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
  102. package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
  103. package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
  104. package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
  105. package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
  106. package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
  107. package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
  108. package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
  109. package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
  110. package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
  111. package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
  112. package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
  113. package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
  114. package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
  115. package/cloudflare/shims/cookie-parser.ts +3 -0
  116. package/cloudflare/shims/cors.ts +21 -0
  117. package/cloudflare/shims/cron.ts +189 -0
  118. package/cloudflare/shims/crypto-js-warn.ts +7 -0
  119. package/cloudflare/shims/did-space-js.ts +17 -0
  120. package/cloudflare/shims/did-space.ts +11 -0
  121. package/cloudflare/shims/error.ts +18 -0
  122. package/cloudflare/shims/express-compat/index.ts +80 -0
  123. package/cloudflare/shims/express-compat/types.ts +41 -0
  124. package/cloudflare/shims/fastq.ts +105 -0
  125. package/cloudflare/shims/lock.ts +115 -0
  126. package/cloudflare/shims/mime-types.ts +56 -0
  127. package/cloudflare/shims/nedb-storage.ts +9 -0
  128. package/cloudflare/shims/node-child-process.ts +9 -0
  129. package/cloudflare/shims/node-fs.ts +20 -0
  130. package/cloudflare/shims/node-http.ts +13 -0
  131. package/cloudflare/shims/node-https.ts +4 -0
  132. package/cloudflare/shims/node-misc.ts +15 -0
  133. package/cloudflare/shims/node-net.ts +8 -0
  134. package/cloudflare/shims/node-os.ts +14 -0
  135. package/cloudflare/shims/node-tty.ts +8 -0
  136. package/cloudflare/shims/node-zlib.ts +17 -0
  137. package/cloudflare/shims/noop.ts +26 -0
  138. package/cloudflare/shims/payment-vendor.ts +14 -0
  139. package/cloudflare/shims/querystring.ts +12 -0
  140. package/cloudflare/shims/queue.ts +585 -0
  141. package/cloudflare/shims/rolldown-runtime.ts +43 -0
  142. package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
  143. package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
  144. package/cloudflare/shims/sequelize-d1/index.ts +34 -0
  145. package/cloudflare/shims/sequelize-d1/model.ts +1157 -0
  146. package/cloudflare/shims/sequelize-d1/operators.ts +293 -0
  147. package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
  148. package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
  149. package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
  150. package/cloudflare/shims/sequelize-d1/types.ts +35 -0
  151. package/cloudflare/shims/stripe-cf.ts +29 -0
  152. package/cloudflare/shims/ws-lite.ts +103 -0
  153. package/cloudflare/shims/xss.ts +3 -0
  154. package/cloudflare/tests/shims/cron.spec.ts +210 -0
  155. package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
  156. package/cloudflare/vite.config.ts +162 -0
  157. package/cloudflare/worker.ts +1553 -0
  158. package/cloudflare/wrangler.json +63 -0
  159. package/cloudflare/wrangler.jsonc +69 -0
  160. package/cloudflare/wrangler.staging.json +66 -0
  161. package/cloudflare/wrangler.toml +28 -0
  162. package/jest.config.js +4 -12
  163. package/package.json +26 -22
  164. package/src/app.tsx +62 -4
  165. package/src/components/customer/link.tsx +9 -13
  166. package/src/components/customer/notification-preference.tsx +3 -2
  167. package/src/components/filter-toolbar.tsx +4 -0
  168. package/src/components/invoice/list.tsx +9 -1
  169. package/src/components/invoice-pdf/utils.ts +2 -1
  170. package/src/components/layout/admin.tsx +39 -5
  171. package/src/components/layout/user-cf.tsx +77 -0
  172. package/src/components/payment-intent/actions.tsx +23 -3
  173. package/src/components/safe-did-address.tsx +75 -0
  174. package/src/libs/patch-user-card.ts +25 -0
  175. package/src/libs/util.ts +5 -7
  176. package/src/pages/admin/billing/meter-events/index.tsx +4 -0
  177. package/src/pages/admin/customers/customers/detail.tsx +2 -2
  178. package/src/pages/admin/customers/customers/index.tsx +2 -2
  179. package/src/pages/admin/overview.tsx +3 -1
  180. package/src/pages/customer/subscription/detail.tsx +4 -4
  181. package/tsconfig.api.json +1 -6
  182. package/tsconfig.json +3 -4
  183. package/tsconfig.types.json +2 -1
  184. package/vite.config.ts +6 -1
@@ -0,0 +1,676 @@
1
+ # Payment Kit → Cloudflare Workers 迁移挑战与解决方案
2
+
3
+ > 生成日期: 2026-03-23
4
+ > 状态: 实施推进中
5
+ > 交叉审查: Claude Opus 4.6 + GPT-5.4 (Codex)
6
+
7
+ **前提**: 这次迁移是必须完成的,不是可选评估。本文档的目标是识别挑战并找到解决方案,而不是论证是否应该迁移。Payment Kit 的现有实现可以改动。
8
+
9
+ ## 目录
10
+
11
+ - [一、执行摘要](#一执行摘要)
12
+ - [二、核心支付流程验证优先级](#二核心支付流程验证优先级)
13
+ - [三、架构差异对照](#三架构差异对照)
14
+ - [四、CF 平台限制(官方文档)](#四cf-平台限制官方文档)
15
+ - [五、支付链路挑战与解决方案](#五支付链路挑战与解决方案)
16
+ - [六、性能瓶颈与优化方向](#六性能瓶颈与优化方向)
17
+ - [七、未实现功能清单](#七未实现功能清单)
18
+ - [八、Shim 层已知缺陷](#八shim-层已知缺陷)
19
+ - [九、解决方案路线图](#九解决方案路线图)
20
+ - [十、合规与审计补充](#十合规与审计补充)
21
+ - [十一、当前进展与下一步](#十一当前进展与下一步)
22
+
23
+ ---
24
+
25
+ ## 一、执行摘要
26
+
27
+ Payment Kit 迁移到 Cloudflare Workers 是确定要做的方向。当前已完成"shim 适配"方案的基础框架(Workers + D1 + esbuild alias),CRUD 管理场景已可工作。
28
+
29
+ **核心支付流程是最应该先验证的部分**——而不是把外围功能都搞完才发现核心不行。本文档梳理了核心支付流程中的挑战,并给出每个挑战的解决方向。Payment Kit 的现有实现不是不可变的,如果现有代码不适合 CF 环境,可以改 Payment Kit 自身。
30
+
31
+ 需要区分两类问题:
32
+ 1. **当前 shim 实现的缺陷** — 可通过改进 shim 或引入更多 CF 组件解决
33
+ 2. **需要改造 Payment Kit 自身的部分** — 将不适合无状态环境的模式改为适合的模式
34
+
35
+ ---
36
+
37
+ ## 二、核心支付流程验证优先级
38
+
39
+ **原则**: 先验证最关键的路径能跑通,再扩展到外围功能。
40
+
41
+ ### 验证顺序
42
+
43
+ | 优先级 | 流程 | 验证内容 | 当前状态 |
44
+ |--------|------|---------|---------|
45
+ | **P0** | Checkout → Stripe 支付 | 创建 session → 填表 → Stripe 扣款 → 回调 → 完成 | ⬜ 待验证 |
46
+ | **P0** | Checkout → 链上支付(TBA) | 创建 session → DID Connect → 链上转账 → 确认 → 完成 | ⬜ 待验证 |
47
+ | **P1** | Stripe Webhook 处理 | payment_intent.succeeded → 更新发票/订阅 | ⬜ 待验证 |
48
+ | **P1** | 订阅创建与续费 | 创建订阅 → 首次支付 → 续费发票 → 自动扣款 | ⬜ 待验证 |
49
+ | **P2** | Credit Grant | 支付成功 → afterCreate hook → 创建 credit grant | ⬜ 待验证 |
50
+ | **P2** | 退款 | 发起退款 → 链上/Stripe 退款 → 状态更新 | ⬜ 待验证 |
51
+ | **P3** | 通知 | 支付成功/失败 → 邮件通知 | ⬜ 待实现 |
52
+ | **P3** | 订阅生命周期 | 试用到期 → 续费提醒 → 取消 | ⬜ 待实现 |
53
+
54
+ ### 验证方法
55
+
56
+ 每个流程需要端到端跑通,对比 Blocklet Server 版本确认结果一致:
57
+ 1. 在 CF Workers 上触发流程
58
+ 2. 检查 D1 数据库状态变更
59
+ 3. 检查外部系统状态(Stripe Dashboard / 链上)
60
+ 4. 对比 Blocklet Server 同一操作的结果
61
+
62
+ ---
63
+
64
+ ## 三、架构差异对照
65
+
66
+ | 维度 | Blocklet Server (Node.js) | Cloudflare Workers |
67
+ |------|--------------------------|-------------------|
68
+ | 进程模型 | 长生命周期单进程 | 请求级 isolate,随时终止 |
69
+ | 内存状态 | 跨请求共享(缓存、锁、队列) | 每请求独立,无共享(可用 DO 协调) |
70
+ | 数据库 | 本地 SQLite(微秒级,支持事务) | D1(毫秒级,仅 batch 原子性) |
71
+ | 后台任务 | process.nextTick / setInterval / 队列 | ctx.waitUntil() / Cron Triggers / Queues / DO Alarms / Workflows |
72
+ | 文件系统 | 完整 fs 访问 | 无文件系统(可用 R2 替代) |
73
+ | 执行时限 | 无限制 | 30s CPU 付费 / 10ms 免费(可配至 5 min) |
74
+ | 并发模型 | 单进程内 AsyncLock 互斥 | 无跨 isolate 互斥(DO 可提供单线程协调) |
75
+ | 网络能力 | 完整 TCP/UDP | 支持 TCP sockets([outbound](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/)),禁止 25 端口 |
76
+ | WebSocket | 完整支持 | Workers 原生支持,DO 用于持久连接和广播 |
77
+
78
+ ---
79
+
80
+ ## 四、CF 平台限制(官方文档)
81
+
82
+ ### 3.1 Workers 运行时
83
+
84
+ | 限制 | 免费计划 | 付费计划 | 官方文档 |
85
+ |------|---------|---------|---------|
86
+ | CPU 时间 | **10 ms** | **30 s**(可配至 5 min) | [Workers Limits](https://developers.cloudflare.com/workers/platform/limits/) |
87
+ | 内存 | **128 MB** | 128 MB | 同上 |
88
+ | 外部 fetch 调用 | 50 次/请求 | **10,000** 次/请求(可配至 10M) | 同上 |
89
+ | `ctx.waitUntil()` | 延长挂钟时间 30s,**不延长 CPU 时间** | 同上 | 同上 |
90
+
91
+ > **关键**: `ctx.waitUntil()` 仅延长挂钟时间,CPU 预算不变。
92
+
93
+ ### 3.2 D1 数据库
94
+
95
+ | 限制 | 值 | 官方文档 |
96
+ |------|-----|---------|
97
+ | 最大数据库大小 | 免费 500 MB / 付费 **10 GB** | [D1 Limits](https://developers.cloudflare.com/d1/platform/limits/) |
98
+ | 单条 SQL 最大长度 | **100 KB** | 同上 |
99
+ | **绑定参数上限** | **100 个** | 同上 |
100
+ | **列数上限** | **100 列** | 同上 |
101
+ | 行/字符串/BLOB 大小 | **2 MB** | 同上 |
102
+ | 查询超时 | **30 秒** | 同上 |
103
+ | 每次调用最大查询数 | 免费 50 / 付费 **1,000** | 同上 |
104
+ | **事务支持** | 仅 `db.batch()` 原子执行,**无交互式 BEGIN/COMMIT/ROLLBACK** | [D1 Batch](https://developers.cloudflare.com/d1/worker-api/d1-database/#batch) |
105
+ | **并发写入** | 单个 D1 数据库为单写入者模型,高并发写入会排队 | 同上 |
106
+
107
+ > **关键**: D1 的 `batch()` 只能预定义一组 SQL 原子执行。无法做"读了之后根据结果决定写什么"的交互式事务。Durable Objects 提供强一致、可串行化存储作为替代。
108
+
109
+ ### 3.3 KV 存储
110
+
111
+ | 限制 | 值 | 官方文档 |
112
+ |------|-----|---------|
113
+ | 一致性模型 | **最终一致** | [KV How It Works](https://developers.cloudflare.com/kv/concepts/how-kv-works/) |
114
+ | 写入传播延迟 | **最多 60 秒** | 同上 |
115
+ | 同一 key 写入频率 | **1 次/秒** | [KV Limits](https://developers.cloudflare.com/kv/platform/limits/) |
116
+ | 最大 value 大小 | 25 MiB | 同上 |
117
+
118
+ > **关键**: KV 写入后最多 60 秒才传播到其他位置。不适合用作分布式锁或幂等检查。
119
+
120
+ ### 3.4 Durable Objects
121
+
122
+ | 限制 | 值 | 官方文档 |
123
+ |------|-----|---------|
124
+ | 存储/对象 | **10 GB** (SQLite) | [DO Limits](https://developers.cloudflare.com/durable-objects/platform/limits/) |
125
+ | CPU/请求 | **30 s**(可配至 5 min) | 同上 |
126
+ | 吞吐量 | ~**1,000 req/s/对象**(软限) | 同上 |
127
+ | 一致性 | **强一致、可串行化** | [DO Overview](https://developers.cloudflare.com/durable-objects/) |
128
+
129
+ > DO 提供单线程协调和强一致存储,可解决 D1 无交互式事务的问题,但每个对象是单点,有吞吐上限。
130
+
131
+ ### 3.5 Queues
132
+
133
+ | 限制 | 值 | 官方文档 |
134
+ |------|-----|---------|
135
+ | 投递保证 | **At-least-once**(非 exactly-once) | [Queues Delivery](https://developers.cloudflare.com/queues/reference/delivery-guarantees/) |
136
+ | 最大重试次数 | 100 | [Queues Limits](https://developers.cloudflare.com/queues/platform/limits/) |
137
+ | 消息大小 | 128 KB | 同上 |
138
+ | 吞吐量 | 5,000 msg/s/队列 | 同上 |
139
+ | 消费者挂钟时间 | **15 分钟** | 同上 |
140
+ | 消息保留 | 免费 24h / 付费 **14 天** | 同上 |
141
+
142
+ > **关键**: 仅 at-least-once,支付系统需要自己做幂等去重。
143
+
144
+ ### 3.6 Cron Triggers
145
+
146
+ | 限制 | 值 | 官方文档 |
147
+ |------|-----|---------|
148
+ | 最小间隔 | **1 分钟** | [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/) |
149
+ | 每账户触发器数 | 免费 5 / 付费 250 | 同上 |
150
+ | 挂钟时间限制 | **15 分钟** | 同上 |
151
+ | 可靠性保证 | **尽力而为**(无 SLA) | 同上 |
152
+
153
+ ### 3.7 Workflows(新组件)
154
+
155
+ | 能力 | 说明 | 官方文档 |
156
+ |------|------|---------|
157
+ | 长时间运行 | 支持等待数小时/数天的 durable steps | [Workflows](https://developers.cloudflare.com/workers/best-practices/workers-best-practices/) |
158
+ | 自动重试 | 每个 step 可独立重试 | 同上 |
159
+ | 外部事件等待 | 支持等待外部回调 | 同上 |
160
+
161
+ > Workflows 理论上可解决链上交易确认和冷钱包审批等长等待场景,但需要将现有代码重构为 step-based 模式。
162
+
163
+ ---
164
+
165
+ ## 五、支付链路挑战与解决方案
166
+
167
+ ### 5.1 EVM 链上交易确认(长轮询)
168
+
169
+ **挑战**: `pay.ts:242` 用游离 Promise 轮询 30 分钟等确认,Worker 会在响应后终止。
170
+ **CF 限制**: CPU 上限 30s,`ctx.waitUntil()` 不延长 CPU。
171
+
172
+ **解决方案(需改造 Payment Kit)**:
173
+ - **方案 A**: 改为"提交即返回 + Cron 对账"模式 — 提交链上交易后标记 `processing`,Cron 定期查询链上状态更新 DB。**改动量小**,只改 `pay.ts` 的确认逻辑。
174
+ - **方案 B**: 使用 CF Workflows(durable steps)— 提交交易作为 step 1,等待确认作为 step 2(可持续数小时)。**更优雅**但需要引入新组件。
175
+ - **方案 C**: 外部链事件监听服务 + webhook 回调。
176
+
177
+ **推荐**: 方案 A 最务实,先跑通 Cron 对账,后续升级到 Workflows。
178
+
179
+ ### 5.2 并发安全(退款双花 / 报价双消费 / 重复发票)
180
+
181
+ **挑战**: 原代码依赖进程内锁(`libs/lock.ts`)和 SQLite 事务做并发保护。D1 无行级锁,无交互式事务。
182
+
183
+ **解决方案(需改造 Payment Kit)**:
184
+ - **乐观锁**: 将 `UPDATE ... WHERE status = 'active'` 替代 read-then-write。如果 `UPDATE` 影响 0 行,说明已被其他请求处理。**改动量中等**,逐个改造关键路径。
185
+ - **唯一约束**: 在 D1 migration 中给关键字段加 UNIQUE(`invoices.metadata_stripe_id`、`refunds.order_id`),INSERT 时用 `ON CONFLICT` 处理。
186
+ - **DO 协调**: 对最关键的路径(如报价消费),用 Durable Object 做单线程协调。
187
+
188
+ **推荐**: 乐观锁 + 唯一约束覆盖 90% 场景,少数核心路径用 DO。
189
+
190
+ ### 5.3 Queue 任务可靠性
191
+
192
+ **挑战**: 当前 shim 选择"不做 startup recovery",孤儿任务会丢失。
193
+
194
+ **解决方案**:
195
+ - **短期**: Cron Trigger 增加孤儿任务扫描(`delay=-1 AND cancelled=false AND age > 5min`)
196
+ - **中期**: 引入 CF Queues 替代内存队列,at-least-once + 幂等去重
197
+ - **改造 Payment Kit**: 所有队列消费者加幂等检查(已有的用 orderId/stripeId,没有的加)
198
+
199
+ ### 5.4 订阅续费
200
+
201
+ **挑战**: 原代码 `while(true)` 轮询调度任务,CF 没有长生命周期进程。
202
+
203
+ **解决方案**:
204
+ - 改造 cron shim 解析各 job 的 cron 表达式,只在匹配时执行
205
+ - 或配置多个 Cron Trigger(每个 job 独立的 cron 表达式)
206
+ - 续费发票创建改为 CF Queues delayed message
207
+
208
+ ### 5.5 安全(P0 发布阻断)
209
+
210
+ | 问题 | 解决方案 | 难度 |
211
+ |------|---------|------|
212
+ | Mock 用户身份 | 实现真实 DID Connect 会话,从 cookie 解析用户 | 中 |
213
+ | 签名验证返回 true | 实现 ED25519 verify(Web Crypto API) | 中 |
214
+ | JWT `.mock` 后缀 | 用真实 HMAC 签名(crypto.subtle) | 低 |
215
+
216
+ > 安全问题是 P0 发布阻断。必须在核心支付流程验证之前解决。
217
+
218
+ ---
219
+
220
+ ## 六、性能瓶颈与优化方向
221
+
222
+ ### 6.1 Submit 流程请求密度
223
+
224
+ 复杂 checkout submit(订阅 + Stripe + 动态定价 + 折扣)单请求内:
225
+
226
+ | 阶段 | 操作量 |
227
+ |------|--------|
228
+ | DB 读写 | 35-55 次 |
229
+ | 外部 HTTP (Stripe API) | 6-12 次 |
230
+ | 外部 HTTP (汇率/链上) | 1-3 次 |
231
+ | **预估总耗时** | **5-15 秒** |
232
+
233
+ D1 每次查询 ~5-20ms 网络往返(vs 本地 SQLite 微秒级),任何一步超时无事务回滚。
234
+
235
+ ### 5.2 内存缓存全部失效
236
+
237
+ | 缓存 | 原始 TTL | CF Workers 行为 |
238
+ |------|---------|----------------|
239
+ | 汇率缓存 | 10 分钟 | 每请求冷启动,每次打 CoinGecko/CMC |
240
+ | baseCurrency | 5 分钟 | 每请求查 D1 |
241
+ | 通知去重 | 24 小时 | 失效,webhook 重试导致重复邮件 |
242
+ | CoinGecko 退避 | 动态 | 失效,频繁触发 rate limit |
243
+
244
+ **优化方向**: KV 缓存(60s 一致性可接受用于汇率/baseCurrency);CoinGecko 退避状态存 KV;Stripe 客户端构建成本低(可接受)。
245
+
246
+ ### 6.3 D1 并发写入瓶颈
247
+
248
+ D1 单数据库为单写入者模型。高并发支付写入(如多个 webhook 同时处理)会排队。这比绑定参数上限 100 对实际支付吞吐影响更大。
249
+
250
+ ---
251
+
252
+ ## 七、未实现功能清单
253
+
254
+ ### 6.1 通知系统 ❌
255
+
256
+ | 组件 | 状态 | 说明 |
257
+ |------|------|------|
258
+ | `Notification.sendToUser()` | Stub | 26 个邮件模板无法发送 |
259
+ | `EventBus.publish()` | Stub | 事件不传播到外部监听器 |
260
+ | `sendToRelay()` | Stub | 实时通知不可达 |
261
+ | SMTP 发送 | 需适配 | Workers 支持 TCP sockets([文档](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/)),但禁止 25 端口;需使用 587/465 端口或 HTTP API 邮件服务 |
262
+
263
+ ### 6.2 实时事件 / WebSocket ⚠️
264
+
265
+ | 组件 | 状态 | 说明 |
266
+ |------|------|------|
267
+ | `useBus('product.created')` | 不触发 | 前端列表不自动刷新 |
268
+ | `broadcast()` | Stub | 管理后台无实时更新 |
269
+ | WebSocket relay | 未实现 | Workers 原生支持 WebSocket([文档](https://developers.cloudflare.com/workers/runtime-apis/websockets/));持久连接和广播需 DO |
270
+
271
+ ### 6.3 用户信息 / DID 组件 ⚠️
272
+
273
+ | 组件 | 状态 | 说明 |
274
+ |------|------|------|
275
+ | `blocklet.getUser(did)` | 返回最小信息 | 仅 DID,无姓名/邮箱/头像 |
276
+ | DID Avatar 组件 | 依赖外部服务 | 需 DID 解析服务可达 |
277
+ | 用户画像同步 | 不工作 | `user.updated` webhook 不触发 |
278
+ | Passkey/WebAuthn | 未实现 | settings 中配置了但无后端处理 |
279
+
280
+ ### 6.4 主题支持 ⚠️
281
+
282
+ | 组件 | 状态 | 说明 |
283
+ |------|------|------|
284
+ | 主题切换 | 硬编码 light | `window.blocklet.theme.prefer='light'` |
285
+ | 自定义调色板 | 不支持 | 无动态主题配置 |
286
+ | 暗色模式 | 不可用 | 固定浅色 |
287
+
288
+ ### 6.5 冷钱包 / 硬件签名 ⚠️
289
+
290
+ | 组件 | 状态 | 说明 |
291
+ |------|------|------|
292
+ | RemoteSigner (EVM) | 代码存在 | 通过 @ocap/wallet.signETH() 实现 |
293
+ | HSM 集成 | 无 | 无硬件安全模块支持 |
294
+ | 多签钱包 | 无 | 无多重签名流程 |
295
+ | APP_SK 保护 | 环境变量 | 明文存储在 CF env,无密钥轮换机制 |
296
+
297
+ > 冷钱包异步审批流程理论上可通过 Workflows 实现(durable steps 支持等待外部事件),但需要重构为 step-based 模式。
298
+
299
+ ### 6.6 文件系统依赖 ❌
300
+
301
+ | 位置 | 用途 | 影响 |
302
+ |------|------|------|
303
+ | `crons/payment-stat.ts` | 锁文件防重复运行 | 无法创建锁文件 |
304
+ | `libs/archive/*` | SQLite 归档文件管理 | 整个归档子系统不可用 |
305
+ | `libs/resource.ts` | 读取 paywall config.json | 启动时可能崩溃 |
306
+ | `libs/archive/executor.ts` | 磁盘空间检查 + 校验和 | 不可用 |
307
+
308
+ ### 6.7 Cron 调度精度 ⚠️
309
+
310
+ | 问题 | 说明 |
311
+ |------|------|
312
+ | 所有 job 每 5 分钟无差别执行 | Shim 缺陷:不区分各 job 的 cron 表达式 |
313
+ | `runOnInit` 不执行 | Shim 缺陷:部署后需要立即运行的 job 被跳过 |
314
+ | 无时区支持 | Cron Triggers 仅 UTC |
315
+ | Cron Trigger "尽力而为" | 平台限制:无 SLA |
316
+
317
+ ---
318
+
319
+ ## 八、Shim 层已知缺陷
320
+
321
+ ### 7.1 sequelize-d1
322
+
323
+ | 缺陷 | 影响 |
324
+ |------|------|
325
+ | `transaction()` 返回假对象 | 4 处调用无原子性 |
326
+ | `LOCK.UPDATE` 静默忽略 | 3 处 FOR UPDATE 失效 |
327
+ | `bulkCreate` 不支持 `updateOnDuplicate` | 统计归档永远无法收敛 |
328
+ | ~~`DataTypes.NOW` 返回 epoch~~ | ✅ 已修复 |
329
+ | ~~`created_at` 排序异常~~ | ✅ 已修复 |
330
+ | `payload->>'value'` PostgreSQL 语法 | meter-event 统计查询报错 |
331
+ | ~~`toJSON()` 泄露内部字段~~ | ✅ 已修复 — 只返回 dataValues + 关联数据 |
332
+ | ~~`attributes.exclude` 不生效~~ | ✅ 已修复 — findAll 后 delete 排除字段 |
333
+ | `afterCreate` hooks 部分路径不触发 | `findByPk` 成功时跳过 hooks |
334
+ | IN 绑定变量 >100 报错 | 可通过分批查询绕过(平台上限 100,但业务可解决) |
335
+
336
+ ### 7.2 Queue Shim
337
+
338
+ | 缺陷 | 影响 |
339
+ |------|------|
340
+ | 无 startup job recovery | 部署后孤儿任务永久丢失(可通过 CF Queues 替代) |
341
+ | `maxRetries` 在同一请求内重试 | 网络瞬断无恢复窗口 |
342
+ | 延迟任务依赖不精确的 Cron | 续费/重试时机不准 |
343
+ | 并发控制选项被忽略 | 所有 job 串行执行 |
344
+
345
+ ### 7.3 Blocklet SDK Shims
346
+
347
+ | Shim | 状态 | 说明 |
348
+ |------|------|------|
349
+ | `env` | ✅ 完整 | 从 CF env 读取 |
350
+ | `wallet` | ✅ 完整 | Proxy 延迟初始化 + fromSecretKey 优雅降级 |
351
+ | `security` | ⚠️ 部分 | decrypt 用长度启发式,可能选错密钥 |
352
+ | `middlewares` | ⚠️ No-op | auth/csrf 不校验 |
353
+ | `notification` | ❌ Stub | 不发送任何通知 |
354
+ | `eventbus` | ❌ Stub | 不传播事件 |
355
+ | `verify-sign` | ❌ Stub | 永远返回 true(**P0 安全风险**) |
356
+ | `auth-service` | ⚠️ 最小 | getUser 仅返回 DID |
357
+ | `component-api` | ❌ Stub | getResources 返回空 |
358
+
359
+ ---
360
+
361
+ ## 九、解决方案路线图
362
+
363
+ ### Phase 1: P0 安全 + 核心支付验证(1-2 周)
364
+
365
+ 目标:让核心支付流程在 CF 上端到端跑通。
366
+
367
+ | 任务 | 改动位置 | 说明 |
368
+ |------|---------|------|
369
+ | 实现真实身份验证 | `worker.ts`, `verify-sign.ts` | 替换 mock auth,实现 ED25519 verify |
370
+ | 加 D1 唯一约束 | D1 migration | `invoices.stripe_id`, `refunds.order_id` 等 |
371
+ | 乐观锁改造 | Payment Kit `checkout-sessions.ts` | `UPDATE quotes SET status='used' WHERE status='active'` 替代事务 |
372
+ | 链上确认改 Cron 对账 | Payment Kit `pay.ts` | 提交即返回 `processing`,Cron 查链上状态 |
373
+ | 验证 Stripe 一次性支付 | 端到端测试 | checkout → Stripe 扣款 → webhook → 完成 |
374
+ | 验证链上支付(TBA) | 端到端测试 | checkout → DID Connect → 转账 → Cron 确认 |
375
+
376
+ ### Phase 2: 订阅 + 队列可靠性(2-3 周)
377
+
378
+ | 任务 | 改动位置 | 说明 |
379
+ |------|---------|------|
380
+ | Cron shim 解析表达式 | `cloudflare/shims/cron.ts` | 各 job 按自己的 cron 时间执行 |
381
+ | 孤儿任务恢复 | `cloudflare/shims/queue.ts` | Cron 扫描 `delay=-1` 未完成任务 |
382
+ | 订阅续费验证 | 端到端测试 | 创建订阅 → 首期支付 → 续费发票 |
383
+ | Credit Grant 验证 | 端到端测试 | 支付成功 → hook → grant 创建 |
384
+ | 引入 CF Queues(可选) | `wrangler.jsonc` + queue consumer | 替代内存队列,提供持久化 |
385
+
386
+ ### Phase 3: 外围功能 + 生产加固(2-3 周)
387
+
388
+ | 任务 | 说明 |
389
+ |------|------|
390
+ | 通知系统 | 接入 Resend/SendGrid 或用 TCP socket (587) |
391
+ | 实时事件 | Workers WebSocket 或轮询方案 |
392
+ | 主题支持 | 从 settings 动态读取 |
393
+ | DID 组件完善 | getUser 返回完整信息 |
394
+ | KV 缓存层 | 汇率、baseCurrency、CoinGecko 退避 |
395
+ | 审计/对账 | 链上 vs DB 定期对账 Cron |
396
+
397
+ ### 需要讨论的架构决策
398
+
399
+ 以下决策需要和团队(CC)讨论:
400
+
401
+ | 决策 | 选项 | 影响 |
402
+ |------|------|------|
403
+ | 链上确认方式 | Cron 对账 vs Workflows vs 外部监听 | 决定链上支付的可靠性和延迟 |
404
+ | 并发控制 | 乐观锁 vs DO | 决定是否引入 DO(成本 + 复杂度) |
405
+ | 队列方案 | 改进 shim vs CF Queues | 决定任务可靠性保证级别 |
406
+ | Payment Kit 可改多少 | 只改 CF 适配 vs 重构核心逻辑 | 决定迁移深度和工作量 |
407
+
408
+ ---
409
+
410
+ ## 十、合规与审计补充
411
+
412
+ ### 10.1 审计与对账能力
413
+
414
+ 支付系统生产运行需要的能力(当前均缺失,需建设):
415
+ - **对账 Cron**: 链上状态与 DB 状态的定期对比
416
+ - **失败任务审查**: 需要日志/告警机制
417
+ - **事件追踪**: 分布式环境下的请求链路追踪(CF Workers 支持 `console.log` → wrangler tail)
418
+
419
+ ### 10.2 密钥治理
420
+
421
+ | 项目 | 当前状态 | 待改进 |
422
+ |------|---------|--------|
423
+ | APP_SK | CF Secrets(wrangler secret put) | Secret Rotation 机制 |
424
+ | Stripe secret key | DB 加密字段,首请求通过 AUTH_SERVICE.getAppEk 拉 EK 后解密 | 无需额外 env,链路已闭合 |
425
+ | Stripe webhook secret | CF Secrets(wrangler secret put) | 每次 endpoint 变更需手动更新 |
426
+ | Secret Rotation | 无 | 需要手动更新 env 和 secrets |
427
+
428
+ ---
429
+
430
+ ## 十一、当前进展与下一步
431
+
432
+ ### 已完成 ✅
433
+
434
+ - Workers + D1 基础框架搭建
435
+ - 267 个 Express 路由挂载
436
+ - DID Connect 14 个支付 action
437
+ - 前端页面渲染(含 logo URL 重写)
438
+ - Media Kit 代理(Service Binding)
439
+ - Stripe API 集成(StripeWrapper + FetchHttpClient)
440
+ - ethers.js JsonRpcProvider 适配(fetch 替代 node:http)
441
+ - DataTypes.NOW 时间戳修复
442
+ - Model.toJSON() 修复(不再泄露 dataValues 内部字段)
443
+ - attributes.exclude 实现(events API 排除大字段)
444
+ - ensureWallet 优雅降级(fromSecretKey 失败不崩溃)
445
+ - 产品/价格/支付方式 CRUD 验证通过
446
+ - 旧域名 URL 自动重写(expressResToResponse 层)
447
+ - CLOUDFLARE_API_TOKEN 持久化(.env)
448
+
449
+ ---
450
+
451
+ ## 十二、三大核心任务详细分析
452
+
453
+ > 以下是当前最需要解决的三个核心领域的深度分析和具体执行计划。
454
+
455
+ ### 核心任务 1: 支付链路
456
+
457
+ #### 1.1 Stripe 支付流程
458
+
459
+ **当前状态**: Stripe SDK 已通过 `StripeWrapper`(注入 `FetchHttpClient`)适配 CF Workers。`validateStripeKeys`、`getStripeClient` 等调用均已验证可工作。
460
+
461
+ **待验证的完整链路**:
462
+
463
+ ```
464
+ 创建 checkout session → 前端填表 → submit → createPaymentIntent
465
+ → Stripe 扣款 → Stripe Webhook 回调 → 更新 invoice/subscription → 完成
466
+ ```
467
+
468
+ **具体风险点**:
469
+
470
+ | 环节 | 风险 | 状态 |
471
+ |------|------|------|
472
+ | submit handler (~900行) | 单请求内 35-55 次 DB + 6-12 次 Stripe API,总耗时 5-15s | ⚠️ 需测试是否超 CPU 限制 |
473
+ | Stripe Webhook 签名验证 | `stripe.webhooks.constructEvent()` 需要 rawBody | ✅ worker.ts 已处理 rawBody |
474
+ | Webhook 幂等 | D1 无事务,重复 webhook 可能导致重复处理 | ⚠️ 需加幂等检查 |
475
+ | createPaymentIntent | 依赖 Stripe API + DB 多步写入 | ⚠️ 无事务保护 |
476
+
477
+ **行动项**:
478
+
479
+ 1. 端到端测试 Stripe 一次性支付(手动触发 checkout → Stripe test card → webhook)
480
+ 2. 在 `checkout-sessions.ts` 的 submit 中加入关键步骤的耗时日志
481
+ 3. Webhook handler 加幂等检查(`WHERE stripe_id = ? AND status != 'paid'`)
482
+
483
+ #### 1.2 EVM 链上支付(TBA/ETH/Base)
484
+
485
+ **当前状态**: ethers.js `defaultGetUrlFunc` 已修复为 fetch-based,`JsonRpcProvider` 可正常连接链上节点。
486
+
487
+ **核心问题: 30 分钟长轮询不兼容 CF Workers**
488
+
489
+ `waitForEvmTxReceipt()` 和 `waitForEvmTxConfirm()` 在 `integrations/ethereum/tx.ts` 中:
490
+ - 每 3 秒轮询一次,**超时 30 分钟**
491
+ - 被 11 个 DID Connect handler 调用(pay/subscribe/setup/collect 等)
492
+ - 当前以 fire-and-forget `.then()` 方式运行,在 CF Workers 中 promise 会被丢弃
493
+
494
+ **影响**: 链上交易提交后,确认逻辑永远不会执行,交易永远停在 `processing` 状态。
495
+
496
+ **解决方案(需改造 Payment Kit)**:
497
+
498
+ ```
499
+ 方案 A(推荐,改动最小): 提交即返回 + Cron 对账
500
+ ├── pay.ts: 提交链上交易后立即标记 processing,不等确认
501
+ ├── 新增 cron job: evm-tx-confirm
502
+ │ ├── 扫描 status=processing 的 payment_intents
503
+ │ ├── 查链上 receipt(单次查询,非轮询)
504
+ │ └── 确认后更新状态 + 触发后续流程
505
+ └── 改动文件: pay.ts, subscribe.ts, tx.ts, crons/index.ts
506
+
507
+ 方案 B: CF Workflows(更优雅,改动大)
508
+ ├── 每笔链上交易创建一个 Workflow instance
509
+ ├── Step 1: 提交交易
510
+ ├── Step 2: sleep + 查询(Workflow 支持长等待)
511
+ └── 需要引入 Workflows 组件,重构为 step-based
512
+ ```
513
+
514
+ **行动项**:
515
+
516
+ 1. **立即**: 实现方案 A — 改造 `tx.ts` 的 `waitForEvmTxReceipt` 为单次查询
517
+ 2. 新增 `crons/evm-tx-confirm.ts`,每 1 分钟扫描待确认交易
518
+ 3. 在 cron shim 中注册新 job
519
+ 4. 端到端测试: DID Connect → 链上转账 → Cron 确认 → 完成
520
+
521
+ #### 1.3 DID Connect 支付 Action
522
+
523
+ **当前状态**: DID Connect auth 路由已挂载,14 个支付 action 已注册。但 action handler 内部的链上确认(1.2 的问题)会导致 EVM 支付不完整。
524
+
525
+ **TBA(ArcBlock 链)支付**: 使用 OCAP GraphQL 查询,不依赖 ethers.js,但同样有轮询确认问题。解决方案同 1.2。
526
+
527
+ ---
528
+
529
+ ### 核心任务 2: 定时任务(Cron)
530
+
531
+ #### 2.1 当前状态
532
+
533
+ **Cron shim 的问题**: 不解析 cron 表达式,`runAll()` 在每次 `*/5 * * * *` 触发时无差别执行所有 16 个 job。
534
+
535
+ **已注册的 16 个 cron job**:
536
+
537
+ | Job | 预期频率 | 当前实际 | runOnInit |
538
+ |-----|---------|---------|-----------|
539
+ | subscription.will.renew | 由 env 配置 | 每 5 分钟 | true (被忽略) |
540
+ | subscription.trial.will.end | 由 env 配置 | 每 5 分钟 | true (被忽略) |
541
+ | customer.subscription.will_canceled | 由 env 配置 | 每 5 分钟 | true (被忽略) |
542
+ | subscription.schedule.retry | 由 env 配置 | 每 5 分钟 | false |
543
+ | checkoutSession.cleanup.expired | 由 env 配置 | 每 5 分钟 | true (被忽略) |
544
+ | stripe.invoice.sync | 由 env 配置 | 每 5 分钟 | false |
545
+ | stripe.payment.sync | 由 env 配置 | 每 5 分钟 | false |
546
+ | stripe.subscription.sync | 由 env 配置 | 每 5 分钟 | false |
547
+ | customer.stake.revoked | 由 env 配置 | 每 5 分钟 | false |
548
+ | payment.stat | 由 env 配置 | 每 5 分钟 | false |
549
+ | payment.daily.report | 由 env 配置 | 每 5 分钟 | false |
550
+ | deposit.vault | 由 env 配置 | 每 5 分钟 | true (被忽略) |
551
+ | credit.consumption | 由 env 配置 | 每 5 分钟 | true (被忽略) |
552
+ | vendor.status.check | 由 env 配置 | 每 5 分钟 | false |
553
+ | vendor.return.scan | 由 env 配置 | 每 5 分钟 | false |
554
+ | data.retention.archive | 条件注册 | 每 5 分钟 | false |
555
+
556
+ **问题总结**:
557
+ 1. **过度执行**: 所有 job 每 5 分钟都跑,本应低频的 job(如 daily report)被频繁执行
558
+ 2. **runOnInit 被忽略**: 部署后需要立即运行的 job 要等到下一个 5 分钟
559
+ 3. **延迟队列 job 从不执行**: queue shim 的 delayed job 写入 DB 但无 cron 来消费
560
+
561
+ #### 2.2 解决方案
562
+
563
+ **方案: cron shim 增加表达式匹配**
564
+
565
+ ```typescript
566
+ // shims/cron.ts — 改造 runAll()
567
+ async runAll() {
568
+ const now = new Date();
569
+ for (const job of this.jobs) {
570
+ if (this.shouldRunNow(job.time, now)) {
571
+ await job.handler();
572
+ }
573
+ }
574
+ }
575
+
576
+ shouldRunNow(cronExpr: string, now: Date): boolean {
577
+ // 解析 cron 表达式,判断当前 5 分钟窗口内是否应执行
578
+ // 可用 cron-parser 或简单手写匹配
579
+ }
580
+ ```
581
+
582
+ **行动项**:
583
+
584
+ 1. cron shim 引入 cron 表达式解析(`cron-parser` 或手写轻量版)
585
+ 2. `runAll()` 改为按表达式判断是否执行
586
+ 3. 新增 delayed-queue-executor cron job:扫描 queue store 中 `will_run_at <= now` 的任务并执行
587
+ 4. 考虑将关键 job 拆分为独立 Cron Trigger(wrangler.jsonc 支持多个 schedule)
588
+
589
+ ---
590
+
591
+ ### 核心任务 3: 事件通知
592
+
593
+ #### 3.1 当前状态
594
+
595
+ | 组件 | 状态 | 调用方 |
596
+ |------|------|--------|
597
+ | `Notification.sendToUser()` | **完全 stub** — 静默丢弃 | 4 个文件调用 |
598
+ | `sendToRelay()` | **完全 stub** | 实时通知 |
599
+ | `EventBus.publish()` | **完全 stub** — 但 API 层实际未使用 | 0 个 API 调用 |
600
+ | WebSocket broadcast | **未实现** | 前端列表不自动刷新 |
601
+
602
+ **影响**: 所有用户通知静默丢失,包括:
603
+ - 订阅即将续费提醒
604
+ - 试用即将到期提醒
605
+ - 订阅取消通知
606
+ - 逾期检测告警
607
+ - 计量订阅告警
608
+
609
+ #### 3.2 Notification 调用链分析
610
+
611
+ ```
612
+ crons/overdue-detection.ts → notification.sendToUser()
613
+ crons/metering-subscription-detection.ts → notification.sendToUser()
614
+ libs/notification/index.ts → notification.sendToUser() (核心通知入口)
615
+ integrations/blocklet/notification.ts → notification.sendToUser()
616
+ ```
617
+
618
+ 核心入口是 `libs/notification/index.ts`,所有业务通知都通过它发出。
619
+
620
+ #### 3.3 解决方案
621
+
622
+ **方案 A(推荐,最快): HTTP API 邮件服务**
623
+
624
+ ```typescript
625
+ // shims/blocklet-sdk/notification.ts
626
+ import { Notification } from '@blocklet/sdk/service/notification';
627
+
628
+ Notification.sendToUser = async (did, notification, opts) => {
629
+ // 通过 HTTP API 发送(Resend / SendGrid / AWS SES)
630
+ const email = await resolveUserEmail(did); // 从 DB 查
631
+ if (!email) return;
632
+
633
+ await fetch('https://api.resend.com/emails', {
634
+ method: 'POST',
635
+ headers: { Authorization: `Bearer ${env.RESEND_API_KEY}` },
636
+ body: JSON.stringify({
637
+ to: email,
638
+ subject: notification.title,
639
+ html: notification.body,
640
+ }),
641
+ });
642
+ };
643
+ ```
644
+
645
+ **方案 B: CF Workers TCP Socket (SMTP 587)**
646
+
647
+ CF Workers 支持 outbound TCP socket,可直接连 SMTP 服务器的 587 端口(TLS)。但实现复杂度高。
648
+
649
+ **方案 C: CF Email Workers**
650
+
651
+ Cloudflare 有 Email Workers 功能,但主要用于接收邮件路由,发送需配合 MailChannels(已停服)或外部 API。
652
+
653
+ **行动项**:
654
+
655
+ 1. 选定邮件服务(推荐 Resend — API 简单、CF Workers 兼容)
656
+ 2. 实现 `notification.ts` shim 的 `sendToUser`
657
+ 3. 从 DB 查 customer email(`customers` 表有 `email` 字段)
658
+ 4. 保留 26 个邮件模板的 HTML 生成逻辑,只替换发送通道
659
+
660
+ ---
661
+
662
+ ## 十三、执行优先级总览
663
+
664
+ | 优先级 | 任务 | 预估工作量 | 依赖 |
665
+ |--------|------|-----------|------|
666
+ | **P0** | Stripe 端到端支付验证 | 1 天 | 无 |
667
+ | **P0** | EVM 链上确认改 Cron 对账 | 2-3 天 | 改造 Payment Kit |
668
+ | **P1** | Cron shim 表达式解析 | 1 天 | 无 |
669
+ | **P1** | 延迟队列 job 执行器 | 0.5 天 | Cron 修复后 |
670
+ | **P1** | Webhook 幂等检查 | 1 天 | 无 |
671
+ | **P2** | 通知系统接入邮件 API | 1-2 天 | 选定邮件服务 |
672
+ | **P2** | D1 唯一约束 migration | 0.5 天 | 无 |
673
+ | **P3** | 乐观锁改造并发路径 | 2-3 天 | 无 |
674
+ | **P3** | WebSocket/实时事件 | 2-3 天 | 需引入 DO |
675
+
676
+ **核心原则**: 先让支付流程跑通,遇到问题逐个解决。不假设 Payment Kit 不可改。