# Payment Kit → Cloudflare Workers 迁移挑战与解决方案

> 生成日期: 2026-03-23
> 状态: 实施推进中
> 交叉审查: Claude Opus 4.6 + GPT-5.4 (Codex)

**前提**: 这次迁移是必须完成的，不是可选评估。本文档的目标是识别挑战并找到解决方案，而不是论证是否应该迁移。Payment Kit 的现有实现可以改动。

## 目录

- [一、执行摘要](#一执行摘要)
- [二、核心支付流程验证优先级](#二核心支付流程验证优先级)
- [三、架构差异对照](#三架构差异对照)
- [四、CF 平台限制（官方文档）](#四cf-平台限制官方文档)
- [五、支付链路挑战与解决方案](#五支付链路挑战与解决方案)
- [六、性能瓶颈与优化方向](#六性能瓶颈与优化方向)
- [七、未实现功能清单](#七未实现功能清单)
- [八、Shim 层已知缺陷](#八shim-层已知缺陷)
- [九、解决方案路线图](#九解决方案路线图)
- [十、合规与审计补充](#十合规与审计补充)
- [十一、当前进展与下一步](#十一当前进展与下一步)

---

## 一、执行摘要

Payment Kit 迁移到 Cloudflare Workers 是确定要做的方向。当前已完成"shim 适配"方案的基础框架（Workers + D1 + esbuild alias），CRUD 管理场景已可工作。

**核心支付流程是最应该先验证的部分**——而不是把外围功能都搞完才发现核心不行。本文档梳理了核心支付流程中的挑战，并给出每个挑战的解决方向。Payment Kit 的现有实现不是不可变的，如果现有代码不适合 CF 环境，可以改 Payment Kit 自身。

需要区分两类问题：
1. **当前 shim 实现的缺陷** — 可通过改进 shim 或引入更多 CF 组件解决
2. **需要改造 Payment Kit 自身的部分** — 将不适合无状态环境的模式改为适合的模式

---

## 二、核心支付流程验证优先级

**原则**: 先验证最关键的路径能跑通，再扩展到外围功能。

### 验证顺序

| 优先级 | 流程 | 验证内容 | 当前状态 |
|--------|------|---------|---------|
| **P0** | Checkout → Stripe 支付 | 创建 session → 填表 → Stripe 扣款 → 回调 → 完成 | ⬜ 待验证 |
| **P0** | Checkout → 链上支付(TBA) | 创建 session → DID Connect → 链上转账 → 确认 → 完成 | ⬜ 待验证 |
| **P1** | Stripe Webhook 处理 | payment_intent.succeeded → 更新发票/订阅 | ⬜ 待验证 |
| **P1** | 订阅创建与续费 | 创建订阅 → 首次支付 → 续费发票 → 自动扣款 | ⬜ 待验证 |
| **P2** | Credit Grant | 支付成功 → afterCreate hook → 创建 credit grant | ⬜ 待验证 |
| **P2** | 退款 | 发起退款 → 链上/Stripe 退款 → 状态更新 | ⬜ 待验证 |
| **P3** | 通知 | 支付成功/失败 → 邮件通知 | ⬜ 待实现 |
| **P3** | 订阅生命周期 | 试用到期 → 续费提醒 → 取消 | ⬜ 待实现 |

### 验证方法

每个流程需要端到端跑通，对比 Blocklet Server 版本确认结果一致：
1. 在 CF Workers 上触发流程
2. 检查 D1 数据库状态变更
3. 检查外部系统状态（Stripe Dashboard / 链上）
4. 对比 Blocklet Server 同一操作的结果

---

## 三、架构差异对照

| 维度 | Blocklet Server (Node.js) | Cloudflare Workers |
|------|--------------------------|-------------------|
| 进程模型 | 长生命周期单进程 | 请求级 isolate，随时终止 |
| 内存状态 | 跨请求共享（缓存、锁、队列） | 每请求独立，无共享（可用 DO 协调） |
| 数据库 | 本地 SQLite（微秒级，支持事务） | D1（毫秒级，仅 batch 原子性） |
| 后台任务 | process.nextTick / setInterval / 队列 | ctx.waitUntil() / Cron Triggers / Queues / DO Alarms / Workflows |
| 文件系统 | 完整 fs 访问 | 无文件系统（可用 R2 替代） |
| 执行时限 | 无限制 | 30s CPU 付费 / 10ms 免费（可配至 5 min） |
| 并发模型 | 单进程内 AsyncLock 互斥 | 无跨 isolate 互斥（DO 可提供单线程协调） |
| 网络能力 | 完整 TCP/UDP | 支持 TCP sockets（[outbound](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/)），禁止 25 端口 |
| WebSocket | 完整支持 | Workers 原生支持，DO 用于持久连接和广播 |

---

## 四、CF 平台限制（官方文档）

### 3.1 Workers 运行时

| 限制 | 免费计划 | 付费计划 | 官方文档 |
|------|---------|---------|---------|
| CPU 时间 | **10 ms** | **30 s**（可配至 5 min） | [Workers Limits](https://developers.cloudflare.com/workers/platform/limits/) |
| 内存 | **128 MB** | 128 MB | 同上 |
| 外部 fetch 调用 | 50 次/请求 | **10,000** 次/请求（可配至 10M） | 同上 |
| `ctx.waitUntil()` | 延长挂钟时间 30s，**不延长 CPU 时间** | 同上 | 同上 |

> **关键**: `ctx.waitUntil()` 仅延长挂钟时间，CPU 预算不变。

### 3.2 D1 数据库

| 限制 | 值 | 官方文档 |
|------|-----|---------|
| 最大数据库大小 | 免费 500 MB / 付费 **10 GB** | [D1 Limits](https://developers.cloudflare.com/d1/platform/limits/) |
| 单条 SQL 最大长度 | **100 KB** | 同上 |
| **绑定参数上限** | **100 个** | 同上 |
| **列数上限** | **100 列** | 同上 |
| 行/字符串/BLOB 大小 | **2 MB** | 同上 |
| 查询超时 | **30 秒** | 同上 |
| 每次调用最大查询数 | 免费 50 / 付费 **1,000** | 同上 |
| **事务支持** | 仅 `db.batch()` 原子执行，**无交互式 BEGIN/COMMIT/ROLLBACK** | [D1 Batch](https://developers.cloudflare.com/d1/worker-api/d1-database/#batch) |
| **并发写入** | 单个 D1 数据库为单写入者模型，高并发写入会排队 | 同上 |

> **关键**: D1 的 `batch()` 只能预定义一组 SQL 原子执行。无法做"读了之后根据结果决定写什么"的交互式事务。Durable Objects 提供强一致、可串行化存储作为替代。

### 3.3 KV 存储

| 限制 | 值 | 官方文档 |
|------|-----|---------|
| 一致性模型 | **最终一致** | [KV How It Works](https://developers.cloudflare.com/kv/concepts/how-kv-works/) |
| 写入传播延迟 | **最多 60 秒** | 同上 |
| 同一 key 写入频率 | **1 次/秒** | [KV Limits](https://developers.cloudflare.com/kv/platform/limits/) |
| 最大 value 大小 | 25 MiB | 同上 |

> **关键**: KV 写入后最多 60 秒才传播到其他位置。不适合用作分布式锁或幂等检查。

### 3.4 Durable Objects

| 限制 | 值 | 官方文档 |
|------|-----|---------|
| 存储/对象 | **10 GB** (SQLite) | [DO Limits](https://developers.cloudflare.com/durable-objects/platform/limits/) |
| CPU/请求 | **30 s**（可配至 5 min） | 同上 |
| 吞吐量 | ~**1,000 req/s/对象**（软限） | 同上 |
| 一致性 | **强一致、可串行化** | [DO Overview](https://developers.cloudflare.com/durable-objects/) |

> DO 提供单线程协调和强一致存储，可解决 D1 无交互式事务的问题，但每个对象是单点，有吞吐上限。

### 3.5 Queues

| 限制 | 值 | 官方文档 |
|------|-----|---------|
| 投递保证 | **At-least-once**（非 exactly-once） | [Queues Delivery](https://developers.cloudflare.com/queues/reference/delivery-guarantees/) |
| 最大重试次数 | 100 | [Queues Limits](https://developers.cloudflare.com/queues/platform/limits/) |
| 消息大小 | 128 KB | 同上 |
| 吞吐量 | 5,000 msg/s/队列 | 同上 |
| 消费者挂钟时间 | **15 分钟** | 同上 |
| 消息保留 | 免费 24h / 付费 **14 天** | 同上 |

> **关键**: 仅 at-least-once，支付系统需要自己做幂等去重。

### 3.6 Cron Triggers

| 限制 | 值 | 官方文档 |
|------|-----|---------|
| 最小间隔 | **1 分钟** | [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/) |
| 每账户触发器数 | 免费 5 / 付费 250 | 同上 |
| 挂钟时间限制 | **15 分钟** | 同上 |
| 可靠性保证 | **尽力而为**（无 SLA） | 同上 |

### 3.7 Workflows（新组件）

| 能力 | 说明 | 官方文档 |
|------|------|---------|
| 长时间运行 | 支持等待数小时/数天的 durable steps | [Workflows](https://developers.cloudflare.com/workers/best-practices/workers-best-practices/) |
| 自动重试 | 每个 step 可独立重试 | 同上 |
| 外部事件等待 | 支持等待外部回调 | 同上 |

> Workflows 理论上可解决链上交易确认和冷钱包审批等长等待场景，但需要将现有代码重构为 step-based 模式。

---

## 五、支付链路挑战与解决方案

### 5.1 EVM 链上交易确认（长轮询）

**挑战**: `pay.ts:242` 用游离 Promise 轮询 30 分钟等确认，Worker 会在响应后终止。
**CF 限制**: CPU 上限 30s，`ctx.waitUntil()` 不延长 CPU。

**解决方案（需改造 Payment Kit）**:
- **方案 A**: 改为"提交即返回 + Cron 对账"模式 — 提交链上交易后标记 `processing`，Cron 定期查询链上状态更新 DB。**改动量小**，只改 `pay.ts` 的确认逻辑。
- **方案 B**: 使用 CF Workflows（durable steps）— 提交交易作为 step 1，等待确认作为 step 2（可持续数小时）。**更优雅**但需要引入新组件。
- **方案 C**: 外部链事件监听服务 + webhook 回调。

**推荐**: 方案 A 最务实，先跑通 Cron 对账，后续升级到 Workflows。

### 5.2 并发安全（退款双花 / 报价双消费 / 重复发票）

**挑战**: 原代码依赖进程内锁（`libs/lock.ts`）和 SQLite 事务做并发保护。D1 无行级锁，无交互式事务。

**解决方案（需改造 Payment Kit）**:
- **乐观锁**: 将 `UPDATE ... WHERE status = 'active'` 替代 read-then-write。如果 `UPDATE` 影响 0 行，说明已被其他请求处理。**改动量中等**，逐个改造关键路径。
- **唯一约束**: 在 D1 migration 中给关键字段加 UNIQUE（`invoices.metadata_stripe_id`、`refunds.order_id`），INSERT 时用 `ON CONFLICT` 处理。
- **DO 协调**: 对最关键的路径（如报价消费），用 Durable Object 做单线程协调。

**推荐**: 乐观锁 + 唯一约束覆盖 90% 场景，少数核心路径用 DO。

### 5.3 Queue 任务可靠性

**挑战**: 当前 shim 选择"不做 startup recovery"，孤儿任务会丢失。

**解决方案**:
- **短期**: Cron Trigger 增加孤儿任务扫描（`delay=-1 AND cancelled=false AND age > 5min`）
- **中期**: 引入 CF Queues 替代内存队列，at-least-once + 幂等去重
- **改造 Payment Kit**: 所有队列消费者加幂等检查（已有的用 orderId/stripeId，没有的加）

### 5.4 订阅续费

**挑战**: 原代码 `while(true)` 轮询调度任务，CF 没有长生命周期进程。

**解决方案**:
- 改造 cron shim 解析各 job 的 cron 表达式，只在匹配时执行
- 或配置多个 Cron Trigger（每个 job 独立的 cron 表达式）
- 续费发票创建改为 CF Queues delayed message

### 5.5 安全（P0 发布阻断）

| 问题 | 解决方案 | 难度 |
|------|---------|------|
| Mock 用户身份 | 实现真实 DID Connect 会话，从 cookie 解析用户 | 中 |
| 签名验证返回 true | 实现 ED25519 verify（Web Crypto API） | 中 |
| JWT `.mock` 后缀 | 用真实 HMAC 签名（crypto.subtle） | 低 |

> 安全问题是 P0 发布阻断。必须在核心支付流程验证之前解决。

---

## 六、性能瓶颈与优化方向

### 6.1 Submit 流程请求密度

复杂 checkout submit（订阅 + Stripe + 动态定价 + 折扣）单请求内：

| 阶段 | 操作量 |
|------|--------|
| DB 读写 | 35-55 次 |
| 外部 HTTP (Stripe API) | 6-12 次 |
| 外部 HTTP (汇率/链上) | 1-3 次 |
| **预估总耗时** | **5-15 秒** |

D1 每次查询 ~5-20ms 网络往返（vs 本地 SQLite 微秒级），任何一步超时无事务回滚。

### 5.2 内存缓存全部失效

| 缓存 | 原始 TTL | CF Workers 行为 |
|------|---------|----------------|
| 汇率缓存 | 10 分钟 | 每请求冷启动，每次打 CoinGecko/CMC |
| baseCurrency | 5 分钟 | 每请求查 D1 |
| 通知去重 | 24 小时 | 失效，webhook 重试导致重复邮件 |
| CoinGecko 退避 | 动态 | 失效，频繁触发 rate limit |

**优化方向**: KV 缓存（60s 一致性可接受用于汇率/baseCurrency）；CoinGecko 退避状态存 KV；Stripe 客户端构建成本低（可接受）。

### 6.3 D1 并发写入瓶颈

D1 单数据库为单写入者模型。高并发支付写入（如多个 webhook 同时处理）会排队。这比绑定参数上限 100 对实际支付吞吐影响更大。

---

## 七、未实现功能清单

### 6.1 通知系统 ❌

| 组件 | 状态 | 说明 |
|------|------|------|
| `Notification.sendToUser()` | Stub | 26 个邮件模板无法发送 |
| `EventBus.publish()` | Stub | 事件不传播到外部监听器 |
| `sendToRelay()` | Stub | 实时通知不可达 |
| SMTP 发送 | 需适配 | Workers 支持 TCP sockets（[文档](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/)），但禁止 25 端口；需使用 587/465 端口或 HTTP API 邮件服务 |

### 6.2 实时事件 / WebSocket ⚠️

| 组件 | 状态 | 说明 |
|------|------|------|
| `useBus('product.created')` | 不触发 | 前端列表不自动刷新 |
| `broadcast()` | Stub | 管理后台无实时更新 |
| WebSocket relay | 未实现 | Workers 原生支持 WebSocket（[文档](https://developers.cloudflare.com/workers/runtime-apis/websockets/)）；持久连接和广播需 DO |

### 6.3 用户信息 / DID 组件 ⚠️

| 组件 | 状态 | 说明 |
|------|------|------|
| `blocklet.getUser(did)` | 返回最小信息 | 仅 DID，无姓名/邮箱/头像 |
| DID Avatar 组件 | 依赖外部服务 | 需 DID 解析服务可达 |
| 用户画像同步 | 不工作 | `user.updated` webhook 不触发 |
| Passkey/WebAuthn | 未实现 | settings 中配置了但无后端处理 |

### 6.4 主题支持 ⚠️

| 组件 | 状态 | 说明 |
|------|------|------|
| 主题切换 | 硬编码 light | `window.blocklet.theme.prefer='light'` |
| 自定义调色板 | 不支持 | 无动态主题配置 |
| 暗色模式 | 不可用 | 固定浅色 |

### 6.5 冷钱包 / 硬件签名 ⚠️

| 组件 | 状态 | 说明 |
|------|------|------|
| RemoteSigner (EVM) | 代码存在 | 通过 @ocap/wallet.signETH() 实现 |
| HSM 集成 | 无 | 无硬件安全模块支持 |
| 多签钱包 | 无 | 无多重签名流程 |
| APP_SK 保护 | 环境变量 | 明文存储在 CF env，无密钥轮换机制 |

> 冷钱包异步审批流程理论上可通过 Workflows 实现（durable steps 支持等待外部事件），但需要重构为 step-based 模式。

### 6.6 文件系统依赖 ❌

| 位置 | 用途 | 影响 |
|------|------|------|
| `crons/payment-stat.ts` | 锁文件防重复运行 | 无法创建锁文件 |
| `libs/archive/*` | SQLite 归档文件管理 | 整个归档子系统不可用 |
| `libs/resource.ts` | 读取 paywall config.json | 启动时可能崩溃 |
| `libs/archive/executor.ts` | 磁盘空间检查 + 校验和 | 不可用 |

### 6.7 Cron 调度精度 ⚠️

| 问题 | 说明 |
|------|------|
| 所有 job 每 5 分钟无差别执行 | Shim 缺陷：不区分各 job 的 cron 表达式 |
| `runOnInit` 不执行 | Shim 缺陷：部署后需要立即运行的 job 被跳过 |
| 无时区支持 | Cron Triggers 仅 UTC |
| Cron Trigger "尽力而为" | 平台限制：无 SLA |

---

## 八、Shim 层已知缺陷

### 7.1 sequelize-d1

| 缺陷 | 影响 |
|------|------|
| `transaction()` 返回假对象 | 4 处调用无原子性 |
| `LOCK.UPDATE` 静默忽略 | 3 处 FOR UPDATE 失效 |
| `bulkCreate` 不支持 `updateOnDuplicate` | 统计归档永远无法收敛 |
| ~~`DataTypes.NOW` 返回 epoch~~ | ✅ 已修复 |
| ~~`created_at` 排序异常~~ | ✅ 已修复 |
| `payload->>'value'` PostgreSQL 语法 | meter-event 统计查询报错 |
| ~~`toJSON()` 泄露内部字段~~ | ✅ 已修复 — 只返回 dataValues + 关联数据 |
| ~~`attributes.exclude` 不生效~~ | ✅ 已修复 — findAll 后 delete 排除字段 |
| `afterCreate` hooks 部分路径不触发 | `findByPk` 成功时跳过 hooks |
| IN 绑定变量 >100 报错 | 可通过分批查询绕过（平台上限 100，但业务可解决） |

### 7.2 Queue Shim

| 缺陷 | 影响 |
|------|------|
| 无 startup job recovery | 部署后孤儿任务永久丢失（可通过 CF Queues 替代） |
| `maxRetries` 在同一请求内重试 | 网络瞬断无恢复窗口 |
| 延迟任务依赖不精确的 Cron | 续费/重试时机不准 |
| 并发控制选项被忽略 | 所有 job 串行执行 |

### 7.3 Blocklet SDK Shims

| Shim | 状态 | 说明 |
|------|------|------|
| `env` | ✅ 完整 | 从 CF env 读取 |
| `wallet` | ✅ 完整 | Proxy 延迟初始化 + fromSecretKey 优雅降级 |
| `security` | ⚠️ 部分 | decrypt 用长度启发式，可能选错密钥 |
| `middlewares` | ⚠️ No-op | auth/csrf 不校验 |
| `notification` | ❌ Stub | 不发送任何通知 |
| `eventbus` | ❌ Stub | 不传播事件 |
| `verify-sign` | ❌ Stub | 永远返回 true（**P0 安全风险**） |
| `auth-service` | ⚠️ 最小 | getUser 仅返回 DID |
| `component-api` | ❌ Stub | getResources 返回空 |

---

## 九、解决方案路线图

### Phase 1: P0 安全 + 核心支付验证（1-2 周）

目标：让核心支付流程在 CF 上端到端跑通。

| 任务 | 改动位置 | 说明 |
|------|---------|------|
| 实现真实身份验证 | `worker.ts`, `verify-sign.ts` | 替换 mock auth，实现 ED25519 verify |
| 加 D1 唯一约束 | D1 migration | `invoices.stripe_id`, `refunds.order_id` 等 |
| 乐观锁改造 | Payment Kit `checkout-sessions.ts` | `UPDATE quotes SET status='used' WHERE status='active'` 替代事务 |
| 链上确认改 Cron 对账 | Payment Kit `pay.ts` | 提交即返回 `processing`，Cron 查链上状态 |
| 验证 Stripe 一次性支付 | 端到端测试 | checkout → Stripe 扣款 → webhook → 完成 |
| 验证链上支付(TBA) | 端到端测试 | checkout → DID Connect → 转账 → Cron 确认 |

### Phase 2: 订阅 + 队列可靠性（2-3 周）

| 任务 | 改动位置 | 说明 |
|------|---------|------|
| Cron shim 解析表达式 | `cloudflare/shims/cron.ts` | 各 job 按自己的 cron 时间执行 |
| 孤儿任务恢复 | `cloudflare/shims/queue.ts` | Cron 扫描 `delay=-1` 未完成任务 |
| 订阅续费验证 | 端到端测试 | 创建订阅 → 首期支付 → 续费发票 |
| Credit Grant 验证 | 端到端测试 | 支付成功 → hook → grant 创建 |
| 引入 CF Queues（可选） | `wrangler.jsonc` + queue consumer | 替代内存队列，提供持久化 |

### Phase 3: 外围功能 + 生产加固（2-3 周）

| 任务 | 说明 |
|------|------|
| 通知系统 | 接入 Resend/SendGrid 或用 TCP socket (587) |
| 实时事件 | Workers WebSocket 或轮询方案 |
| 主题支持 | 从 settings 动态读取 |
| DID 组件完善 | getUser 返回完整信息 |
| KV 缓存层 | 汇率、baseCurrency、CoinGecko 退避 |
| 审计/对账 | 链上 vs DB 定期对账 Cron |

### 需要讨论的架构决策

以下决策需要和团队（CC）讨论：

| 决策 | 选项 | 影响 |
|------|------|------|
| 链上确认方式 | Cron 对账 vs Workflows vs 外部监听 | 决定链上支付的可靠性和延迟 |
| 并发控制 | 乐观锁 vs DO | 决定是否引入 DO（成本 + 复杂度） |
| 队列方案 | 改进 shim vs CF Queues | 决定任务可靠性保证级别 |
| Payment Kit 可改多少 | 只改 CF 适配 vs 重构核心逻辑 | 决定迁移深度和工作量 |

---

## 十、合规与审计补充

### 10.1 审计与对账能力

支付系统生产运行需要的能力（当前均缺失，需建设）：
- **对账 Cron**: 链上状态与 DB 状态的定期对比
- **失败任务审查**: 需要日志/告警机制
- **事件追踪**: 分布式环境下的请求链路追踪（CF Workers 支持 `console.log` → wrangler tail）

### 10.2 密钥治理

| 项目 | 当前状态 | 待改进 |
|------|---------|--------|
| APP_SK | CF Secrets（wrangler secret put） | Secret Rotation 机制 |
| Stripe secret key | DB 加密字段，首请求通过 AUTH_SERVICE.getAppEk 拉 EK 后解密 | 无需额外 env，链路已闭合 |
| Stripe webhook secret | CF Secrets（wrangler secret put） | 每次 endpoint 变更需手动更新 |
| Secret Rotation | 无 | 需要手动更新 env 和 secrets |

---

## 十一、当前进展与下一步

### 已完成 ✅

- Workers + D1 基础框架搭建
- 267 个 Express 路由挂载
- DID Connect 14 个支付 action
- 前端页面渲染（含 logo URL 重写）
- Media Kit 代理（Service Binding）
- Stripe API 集成（StripeWrapper + FetchHttpClient）
- ethers.js JsonRpcProvider 适配（fetch 替代 node:http）
- DataTypes.NOW 时间戳修复
- Model.toJSON() 修复（不再泄露 dataValues 内部字段）
- attributes.exclude 实现（events API 排除大字段）
- ensureWallet 优雅降级（fromSecretKey 失败不崩溃）
- 产品/价格/支付方式 CRUD 验证通过
- 旧域名 URL 自动重写（expressResToResponse 层）
- CLOUDFLARE_API_TOKEN 持久化（.env）

---

## 十二、三大核心任务详细分析

> 以下是当前最需要解决的三个核心领域的深度分析和具体执行计划。

### 核心任务 1: 支付链路

#### 1.1 Stripe 支付流程

**当前状态**: Stripe SDK 已通过 `StripeWrapper`（注入 `FetchHttpClient`）适配 CF Workers。`validateStripeKeys`、`getStripeClient` 等调用均已验证可工作。

**待验证的完整链路**:

```
创建 checkout session → 前端填表 → submit → createPaymentIntent
→ Stripe 扣款 → Stripe Webhook 回调 → 更新 invoice/subscription → 完成
```

**具体风险点**:

| 环节 | 风险 | 状态 |
|------|------|------|
| submit handler (~900行) | 单请求内 35-55 次 DB + 6-12 次 Stripe API，总耗时 5-15s | ⚠️ 需测试是否超 CPU 限制 |
| Stripe Webhook 签名验证 | `stripe.webhooks.constructEvent()` 需要 rawBody | ✅ worker.ts 已处理 rawBody |
| Webhook 幂等 | D1 无事务，重复 webhook 可能导致重复处理 | ⚠️ 需加幂等检查 |
| createPaymentIntent | 依赖 Stripe API + DB 多步写入 | ⚠️ 无事务保护 |

**行动项**:

1. 端到端测试 Stripe 一次性支付（手动触发 checkout → Stripe test card → webhook）
2. 在 `checkout-sessions.ts` 的 submit 中加入关键步骤的耗时日志
3. Webhook handler 加幂等检查（`WHERE stripe_id = ? AND status != 'paid'`）

#### 1.2 EVM 链上支付（TBA/ETH/Base）

**当前状态**: ethers.js `defaultGetUrlFunc` 已修复为 fetch-based，`JsonRpcProvider` 可正常连接链上节点。

**核心问题: 30 分钟长轮询不兼容 CF Workers**

`waitForEvmTxReceipt()` 和 `waitForEvmTxConfirm()` 在 `integrations/ethereum/tx.ts` 中：
- 每 3 秒轮询一次，**超时 30 分钟**
- 被 11 个 DID Connect handler 调用（pay/subscribe/setup/collect 等）
- 当前以 fire-and-forget `.then()` 方式运行，在 CF Workers 中 promise 会被丢弃

**影响**: 链上交易提交后，确认逻辑永远不会执行，交易永远停在 `processing` 状态。

**解决方案（需改造 Payment Kit）**:

```
方案 A（推荐，改动最小）: 提交即返回 + Cron 对账
├── pay.ts: 提交链上交易后立即标记 processing，不等确认
├── 新增 cron job: evm-tx-confirm
│   ├── 扫描 status=processing 的 payment_intents
│   ├── 查链上 receipt（单次查询，非轮询）
│   └── 确认后更新状态 + 触发后续流程
└── 改动文件: pay.ts, subscribe.ts, tx.ts, crons/index.ts

方案 B: CF Workflows（更优雅，改动大）
├── 每笔链上交易创建一个 Workflow instance
├── Step 1: 提交交易
├── Step 2: sleep + 查询（Workflow 支持长等待）
└── 需要引入 Workflows 组件，重构为 step-based
```

**行动项**:

1. **立即**: 实现方案 A — 改造 `tx.ts` 的 `waitForEvmTxReceipt` 为单次查询
2. 新增 `crons/evm-tx-confirm.ts`，每 1 分钟扫描待确认交易
3. 在 cron shim 中注册新 job
4. 端到端测试: DID Connect → 链上转账 → Cron 确认 → 完成

#### 1.3 DID Connect 支付 Action

**当前状态**: DID Connect auth 路由已挂载，14 个支付 action 已注册。但 action handler 内部的链上确认（1.2 的问题）会导致 EVM 支付不完整。

**TBA（ArcBlock 链）支付**: 使用 OCAP GraphQL 查询，不依赖 ethers.js，但同样有轮询确认问题。解决方案同 1.2。

---

### 核心任务 2: 定时任务（Cron）

#### 2.1 当前状态

**Cron shim 的问题**: 不解析 cron 表达式，`runAll()` 在每次 `*/5 * * * *` 触发时无差别执行所有 16 个 job。

**已注册的 16 个 cron job**:

| Job | 预期频率 | 当前实际 | runOnInit |
|-----|---------|---------|-----------|
| subscription.will.renew | 由 env 配置 | 每 5 分钟 | true (被忽略) |
| subscription.trial.will.end | 由 env 配置 | 每 5 分钟 | true (被忽略) |
| customer.subscription.will_canceled | 由 env 配置 | 每 5 分钟 | true (被忽略) |
| subscription.schedule.retry | 由 env 配置 | 每 5 分钟 | false |
| checkoutSession.cleanup.expired | 由 env 配置 | 每 5 分钟 | true (被忽略) |
| stripe.invoice.sync | 由 env 配置 | 每 5 分钟 | false |
| stripe.payment.sync | 由 env 配置 | 每 5 分钟 | false |
| stripe.subscription.sync | 由 env 配置 | 每 5 分钟 | false |
| customer.stake.revoked | 由 env 配置 | 每 5 分钟 | false |
| payment.stat | 由 env 配置 | 每 5 分钟 | false |
| payment.daily.report | 由 env 配置 | 每 5 分钟 | false |
| deposit.vault | 由 env 配置 | 每 5 分钟 | true (被忽略) |
| credit.consumption | 由 env 配置 | 每 5 分钟 | true (被忽略) |
| vendor.status.check | 由 env 配置 | 每 5 分钟 | false |
| vendor.return.scan | 由 env 配置 | 每 5 分钟 | false |
| data.retention.archive | 条件注册 | 每 5 分钟 | false |

**问题总结**:
1. **过度执行**: 所有 job 每 5 分钟都跑，本应低频的 job（如 daily report）被频繁执行
2. **runOnInit 被忽略**: 部署后需要立即运行的 job 要等到下一个 5 分钟
3. **延迟队列 job 从不执行**: queue shim 的 delayed job 写入 DB 但无 cron 来消费

#### 2.2 解决方案

**方案: cron shim 增加表达式匹配**

```typescript
// shims/cron.ts — 改造 runAll()
async runAll() {
  const now = new Date();
  for (const job of this.jobs) {
    if (this.shouldRunNow(job.time, now)) {
      await job.handler();
    }
  }
}

shouldRunNow(cronExpr: string, now: Date): boolean {
  // 解析 cron 表达式，判断当前 5 分钟窗口内是否应执行
  // 可用 cron-parser 或简单手写匹配
}
```

**行动项**:

1. cron shim 引入 cron 表达式解析（`cron-parser` 或手写轻量版）
2. `runAll()` 改为按表达式判断是否执行
3. 新增 delayed-queue-executor cron job：扫描 queue store 中 `will_run_at <= now` 的任务并执行
4. 考虑将关键 job 拆分为独立 Cron Trigger（wrangler.jsonc 支持多个 schedule）

---

### 核心任务 3: 事件通知

#### 3.1 当前状态

| 组件 | 状态 | 调用方 |
|------|------|--------|
| `Notification.sendToUser()` | **完全 stub** — 静默丢弃 | 4 个文件调用 |
| `sendToRelay()` | **完全 stub** | 实时通知 |
| `EventBus.publish()` | **完全 stub** — 但 API 层实际未使用 | 0 个 API 调用 |
| WebSocket broadcast | **未实现** | 前端列表不自动刷新 |

**影响**: 所有用户通知静默丢失，包括：
- 订阅即将续费提醒
- 试用即将到期提醒
- 订阅取消通知
- 逾期检测告警
- 计量订阅告警

#### 3.2 Notification 调用链分析

```
crons/overdue-detection.ts → notification.sendToUser()
crons/metering-subscription-detection.ts → notification.sendToUser()
libs/notification/index.ts → notification.sendToUser() (核心通知入口)
integrations/blocklet/notification.ts → notification.sendToUser()
```

核心入口是 `libs/notification/index.ts`，所有业务通知都通过它发出。

#### 3.3 解决方案

**方案 A（推荐，最快）: HTTP API 邮件服务**

```typescript
// shims/blocklet-sdk/notification.ts
import { Notification } from '@blocklet/sdk/service/notification';

Notification.sendToUser = async (did, notification, opts) => {
  // 通过 HTTP API 发送（Resend / SendGrid / AWS SES）
  const email = await resolveUserEmail(did); // 从 DB 查
  if (!email) return;

  await fetch('https://api.resend.com/emails', {
    method: 'POST',
    headers: { Authorization: `Bearer ${env.RESEND_API_KEY}` },
    body: JSON.stringify({
      to: email,
      subject: notification.title,
      html: notification.body,
    }),
  });
};
```

**方案 B: CF Workers TCP Socket (SMTP 587)**

CF Workers 支持 outbound TCP socket，可直接连 SMTP 服务器的 587 端口（TLS）。但实现复杂度高。

**方案 C: CF Email Workers**

Cloudflare 有 Email Workers 功能，但主要用于接收邮件路由，发送需配合 MailChannels（已停服）或外部 API。

**行动项**:

1. 选定邮件服务（推荐 Resend — API 简单、CF Workers 兼容）
2. 实现 `notification.ts` shim 的 `sendToUser`
3. 从 DB 查 customer email（`customers` 表有 `email` 字段）
4. 保留 26 个邮件模板的 HTML 生成逻辑，只替换发送通道

---

## 十三、执行优先级总览

| 优先级 | 任务 | 预估工作量 | 依赖 |
|--------|------|-----------|------|
| **P0** | Stripe 端到端支付验证 | 1 天 | 无 |
| **P0** | EVM 链上确认改 Cron 对账 | 2-3 天 | 改造 Payment Kit |
| **P1** | Cron shim 表达式解析 | 1 天 | 无 |
| **P1** | 延迟队列 job 执行器 | 0.5 天 | Cron 修复后 |
| **P1** | Webhook 幂等检查 | 1 天 | 无 |
| **P2** | 通知系统接入邮件 API | 1-2 天 | 选定邮件服务 |
| **P2** | D1 唯一约束 migration | 0.5 天 | 无 |
| **P3** | 乐观锁改造并发路径 | 2-3 天 | 无 |
| **P3** | WebSocket/实时事件 | 2-3 天 | 需引入 DO |

**核心原则**: 先让支付流程跑通，遇到问题逐个解决。不假设 Payment Kit 不可改。
