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,602 @@
1
+ # Staging 迁移执行手册 — AIGNE Hub (Media Kit + Payment Kit 部分)
2
+
3
+ > **受众**:Shijun
4
+ > **目标**:把 staging AIGNE Hub 的 Media Kit 和 Payment Kit 从 AWS Blocklet Server 完整迁移到 Cloudflare Workers
5
+ > **范围**:本手册**只覆盖** Media Kit 和 Payment Kit。blocklet-service(DID)部分由 DID 团队负责,假设执行本手册前 **staging blocklet-service 已经部署在 CF**。
6
+ > **预计时间**:3~5 小时(首次执行)
7
+ > **前置阅读**:`README.md`(部署架构)、`MIGRATION-RUNBOOK.md`(迁移原理)、`MIGRATION-CHALLENGES.md`(已知坑)
8
+
9
+ ---
10
+
11
+ ## 📋 执行前必须确认
12
+
13
+ 在动手前**逐项确认**下面 6 件事,任何一项不满足都不要开始:
14
+
15
+ | # | 确认项 | 确认方法 | 不满足怎么办 |
16
+ |---|--------|---------|-------------|
17
+ | 1 | AWS staging 访问权限 | `ssh <TODO: AWS_STAGING_HOST>` 能连上 | 找运维要跳板机权限 |
18
+ | 2 | staging blocklet-service 已在 CF 上 | `wrangler deployments list --name <TODO: STAGING_BLOCKLET_SERVICE_WORKER_NAME>` 有记录 | **停止**,等 DID 团队通知 |
19
+ | 3 | blocklet-service 的 `BlockletServiceRPC` entrypoint 可用 | DID 团队确认 `registerApp` / `getAppEk` / `resolveIdentity` 方法已实现 | **停止**,等 DID 团队通知 |
20
+ | 4 | Cloudflare 账号已登录 | `wrangler whoami` 返回预期账号 | `wrangler login` |
21
+ | 5 | Payment Kit 仓库已 clone 且 CF 分支已构建过 | `cd blocklets/core/cloudflare && node run-build.js` 本地能跑通 | 参考 README.md Phase 4 |
22
+ | 6 | Stripe staging 账号有权限改 webhook endpoint | 能登录 Stripe Dashboard staging 账号 | 找 Stripe 账号管理者 |
23
+
24
+ **确认完毕**后再进入 Phase 0。
25
+
26
+ ---
27
+
28
+ ## Phase 0:AWS staging 信息收集(10 min)
29
+
30
+ SSH 上 AWS,拿下面这些值,记到本地 **staging-migration-secrets.txt**(不要提交):
31
+
32
+ ```bash
33
+ ssh <TODO: AWS_STAGING_HOST>
34
+
35
+ echo $BLOCKLET_APP_ID # APP_DID
36
+ echo $BLOCKLET_APP_PID # APP_PID(后续 registerApp 用)
37
+ echo $BLOCKLET_APP_SK # APP_SK(关键,后续 registerApp + CF secret 都用)
38
+ find $BLOCKLET_DATA_DIR -name "*.db" # 定位 Payment Kit 数据库
39
+ ```
40
+
41
+ 如果 `$BLOCKLET_APP_SK` 为空,从 `~/.blocklet/core/server.db` 读:
42
+
43
+ ```bash
44
+ sqlite3 ~/.blocklet/core/server.db \
45
+ "SELECT appSk FROM blocklets WHERE appDid='$BLOCKLET_APP_ID'"
46
+ ```
47
+
48
+ 如果 `APP_DID ≠ APP_PID`(SK 曾轮转),额外记下 `$BLOCKLET_APP_PSK`。
49
+
50
+ ## Phase 1:验证密钥派生一致性(5 min)
51
+
52
+ **关键**:如果导错 SK,后面全部白干。在本地机器上跑:
53
+
54
+ ```bash
55
+ cd /tmp && mkdir did-verify && cd did-verify
56
+ npm init -y
57
+ npm install @ocap/wallet @ocap/mcrypto
58
+
59
+ node -e "
60
+ const { fromSecretKey } = require('@ocap/wallet');
61
+ const { types } = require('@ocap/mcrypto');
62
+ const sk = '<Phase 0 拿到的 APP_SK>';
63
+ const wallet = fromSecretKey(sk, {
64
+ role: types.RoleType.ROLE_APPLICATION,
65
+ pk: types.KeyType.ED25519,
66
+ hash: types.HashType.SHA3,
67
+ });
68
+ console.log('Derived APP DID:', wallet.address);
69
+ console.log('Expected APP DID:', '<Phase 0 拿到的 APP_DID>');
70
+ console.log('Match:', wallet.address === '<APP_DID>');
71
+ "
72
+ ```
73
+
74
+ - **`Match: true`** → 继续 Phase 2
75
+ - **`Match: false`** → **停止**。重新确认 `APP_SK` 是不是拿错了(可能拿到的是 server 的 SK,不是 blocklet 的)
76
+
77
+ ## Phase 2:在 staging blocklet-service 上注册 Payment Kit(20 min)
78
+
79
+ 这一步**必须在部署 Payment Kit Worker 之前**完成。blocklet-service 需要知道 Payment Kit 的身份,后续 Payment Kit Worker 启动时 `AUTH_SERVICE.getAppEk(APP_PID)` 才能拿到 EK 初始化 Stripe 加密链。
80
+
81
+ ### 2.1 准备注册参数
82
+
83
+ ```
84
+ APP_PID: <Phase 0 拿到的 PID,复用 AWS 原值>
85
+ APP_SK: <Phase 0 拿到的 hex>
86
+ APP_NAME: Payment Kit
87
+ APP_DESC: Payment Kit (staging)
88
+ APP_URL: https://payment-kit-staging.<your-account>.workers.dev
89
+ (先用 workers.dev,验证通过后再切自定义域名)
90
+ ```
91
+
92
+ ### 2.2 调用 registerApp
93
+
94
+ > **⚠️ Shijun 需要和 DID 团队确认注册方式**。下面是两种可能的路径,具体哪种由 blocklet-service 的实现决定:
95
+
96
+ **方式 A:通过 blocklet-service admin UI**
97
+
98
+ 1. 访问 `https://<TODO: STAGING_BLOCKLET_SERVICE_URL>/admin`
99
+ 2. 登录(DID 团队给的 admin 账号)
100
+ 3. 找到 "Apps" / "Blocklets" 管理页
101
+ 4. 点击 "Register App",填入上面的参数
102
+ 5. 上传 APP_SK(或粘贴 hex),确认 derived DID 与 APP_PID 一致
103
+
104
+ **方式 B:通过 RPC 调用**
105
+
106
+ ```bash
107
+ # 如果 blocklet-service 暴露了注册 API:
108
+ curl -X POST https://<TODO: STAGING_BLOCKLET_SERVICE_URL>/admin/api/apps \
109
+ -H "Authorization: Bearer <admin token>" \
110
+ -H "Content-Type: application/json" \
111
+ -d '{
112
+ "appPid": "<APP_PID>",
113
+ "appSk": "<APP_SK>",
114
+ "name": "Payment Kit",
115
+ "description": "Payment Kit (staging)",
116
+ "url": "https://payment-kit-staging.<account>.workers.dev"
117
+ }'
118
+ ```
119
+
120
+ ### 2.3 验证注册成功
121
+
122
+ ```bash
123
+ # 假设 DID 团队提供了查询接口,或直接登录 admin UI 看列表
124
+ curl https://<TODO: STAGING_BLOCKLET_SERVICE_URL>/admin/api/apps/<APP_PID>
125
+ # 预期返回:{ pid, name, url, ek_initialized: true, ... }
126
+ ```
127
+
128
+ **关键 1**:确认注册后该 APP_PID 对应的 **EK 已经初始化**。如果 EK 没生成,后续 Payment Kit Worker 首请求会打 `[Security] No APP_EK found`。
129
+
130
+ ### 2.4 ⚠️ 和 DID 团队确认 EK 与 AWS 一致
131
+
132
+ > "我用 AWS 原 `APP_SK` + 原 `APP_PID` 注册后,`getAppEk(APP_PID)` 返回的值和 AWS Blocklet Server 上该 blocklet 的 EK 是同一个吗?"
133
+
134
+ **EK 必须一致**,否则 `payment_methods` 表里加密的 Stripe secret key 无法解密。如果 DID 团队的回答是 "不一致",**停止迁移**,让他们把 AWS 原 EK 手动写入 staging blocklet-service 再继续。原 EK 取法:
135
+
136
+ ```bash
137
+ ssh <AWS_STAGING_HOST>
138
+ echo $BLOCKLET_APP_EK
139
+ # 或: sqlite3 ~/.blocklet/core/server.db "SELECT appEk FROM blocklets WHERE appDid='$BLOCKLET_APP_ID'"
140
+ ```
141
+
142
+ ## Phase 3:部署 Media Kit staging(参考外部文档)
143
+
144
+ Media Kit 有自己的 CF Workers 部署方式:
145
+
146
+ 1. **切到 media-kit 仓库**:`cd <blocklet/media-kit checkout>`
147
+ 2. **参考该仓库的 `cloudflare/README.md`** 完成 staging 部署
148
+ 3. **执行完成后,记录以下信息**:
149
+
150
+ ```
151
+ MEDIA_KIT_WORKER_NAME=<例如: media-kit-staging>
152
+ MEDIA_KIT_URL=https://<media-kit-staging>.<account>.workers.dev
153
+ ```
154
+
155
+ 4. **验证 Media Kit staging 可访问**:
156
+
157
+ ```bash
158
+ curl https://<MEDIA_KIT_URL>/
159
+ # 预期:200 或 Media Kit 的欢迎响应(非 522/404)
160
+ ```
161
+
162
+ > **如果 Media Kit 仓库没有 staging 配置模板**,Shijun 需要基于 `wrangler.jsonc` 复制一份 `wrangler.staging.jsonc`,改 name 和资源 ID。具体以 Media Kit 仓库的 README 为准。
163
+
164
+ ## Phase 4:Payment Kit — 创建 Cloudflare 资源(10 min)
165
+
166
+ ```bash
167
+ cd <payment-kit-repo>/blocklets/core/cloudflare
168
+
169
+ # 4.1 D1 数据库
170
+ wrangler d1 create payment-kit-staging
171
+ # 记下返回的 database_id,记为 $D1_ID
172
+
173
+ # 4.2 KV Namespace(DID Connect session)
174
+ wrangler kv namespace create payment-kit-staging-didconnect
175
+ # 记下返回的 id,记为 $KV_ID
176
+
177
+ # 4.3 CF Queue 主队列 + DLQ
178
+ wrangler queues create payment-kit-staging-jobs
179
+ wrangler queues create payment-kit-staging-jobs-dlq
180
+ ```
181
+
182
+ **本地记录资源 ID**:
183
+
184
+ ```
185
+ D1_ID=<...>
186
+ KV_ID=<...>
187
+ ```
188
+
189
+ ## Phase 5:Payment Kit — 导出 AWS 数据(20 min)
190
+
191
+ ### 5.1 在 AWS 上导出全表
192
+
193
+ ```bash
194
+ ssh <AWS_STAGING_HOST>
195
+ PAYMENT_DB=<Phase 0 找到的路径>
196
+
197
+ mkdir -p /tmp/pk-mig/sql && cd /tmp/pk-mig
198
+
199
+ # 导出所有业务表为幂等 INSERT
200
+ for t in $(sqlite3 $PAYMENT_DB "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'SequelizeMeta'"); do
201
+ sqlite3 $PAYMENT_DB ".mode insert $t" "SELECT * FROM $t" \
202
+ | sed 's/^INSERT INTO /INSERT OR IGNORE INTO /' > "sql/${t}.sql"
203
+ echo "$t: $(grep -c '^INSERT' sql/${t}.sql)"
204
+ done > row-counts.txt
205
+
206
+ tar czf pk-data.tar.gz sql/ row-counts.txt
207
+ ```
208
+
209
+ ### 5.2 下载到本地
210
+
211
+ ```bash
212
+ scp <AWS_STAGING_HOST>:/tmp/pk-mig/pk-data.tar.gz .
213
+ tar xzf pk-data.tar.gz
214
+ ```
215
+
216
+ ### 5.3 对大表分片
217
+
218
+ D1 单条 SQL 上限 100KB,超过 500 行 INSERT 的表要切片:
219
+
220
+ ```bash
221
+ for t in credit_transactions invoice_items invoices meter_events payment_intents payment_stats price_quotes webhook_attempts; do
222
+ F="sql/${t}.sql"
223
+ [ -f "$F" ] && [ $(wc -l < "$F") -gt 500 ] && {
224
+ split -l 500 -d "$F" "sql/${t}.part-"
225
+ for p in sql/${t}.part-*; do mv "$p" "${p}.sql"; done
226
+ rm "$F"
227
+ }
228
+ done
229
+ ```
230
+
231
+ ### 5.4 替换内网地址
232
+
233
+ ```bash
234
+ grep -rln '192\.168\.\|localhost\|127\.0\.0\.1\|10\.0\.' sql/
235
+ ```
236
+
237
+ 重点检查 `exchange_rate_providers.sql`、`webhook_endpoints.sql`、`settings.sql`。找到后手动编辑,把内网 URL 换成公网端点(比如汇率 API 换成 coingecko)。
238
+
239
+ ## Phase 6:Payment Kit — 部署 D1 Schema(5 min)
240
+
241
+ 在本地仓库执行:
242
+
243
+ ```bash
244
+ cd <payment-kit-repo>/blocklets/core/cloudflare
245
+
246
+ # 6.1 应用 migration(表结构 + 索引 + 约束)
247
+ wrangler d1 migrations apply payment-kit-staging --remote
248
+
249
+ # 预期输出:0001_initial_schema.sql / 0002_indexes.sql / 0003_locks_and_constraints.sql 全部成功
250
+
251
+ # 6.2 验证表数量
252
+ wrangler d1 execute payment-kit-staging --remote \
253
+ --command "SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table'"
254
+
255
+ # 预期:40 张左右(38 业务表 + _did_connect_tokens + d1_migrations)
256
+ ```
257
+
258
+ ## Phase 7:Payment Kit — 导入数据(1~2 hr)
259
+
260
+ 按"基础表 → 业务表 → 交易/日志表"顺序导入,避开外键依赖问题。分片文件优先:
261
+
262
+ ```bash
263
+ cd <payment-kit-repo>/blocklets/core/cloudflare
264
+ SQL_DIR=<Phase 5.2 解压的 sql 目录绝对路径>
265
+
266
+ import() {
267
+ # 优先导入分片
268
+ if ls "$SQL_DIR/$1.part-"*.sql 2>/dev/null | head -1 >/dev/null; then
269
+ for p in "$SQL_DIR/$1.part-"*.sql; do
270
+ wrangler d1 execute payment-kit-staging --remote --file="$p"
271
+ done
272
+ elif [ -f "$SQL_DIR/$1.sql" ]; then
273
+ wrangler d1 execute payment-kit-staging --remote --file="$SQL_DIR/$1.sql"
274
+ fi
275
+ }
276
+
277
+ # 基础表
278
+ for t in customers products prices meters coupons tax_rates payment_methods payment_currencies exchange_rate_providers settings; do import $t; done
279
+
280
+ # 业务表
281
+ for t in subscriptions subscription_items discounts promotion_codes product_vendors pricing_tables payment_links checkout_sessions setup_intents auto_recharge_configs credit_grants; do import $t; done
282
+
283
+ # 交易/日志表
284
+ for t in invoices invoice_items payment_intents price_quotes payment_stats credit_transactions meter_events webhook_endpoints webhook_attempts refunds payouts revenue_snapshots usage_records jobs archive_locks archive_metadata locks; do import $t; done
285
+ ```
286
+
287
+ **行数校验**:
288
+
289
+ ```bash
290
+ # 对几张关键表抽查即可
291
+ for t in customers invoices subscriptions payment_intents; do
292
+ wrangler d1 execute payment-kit-staging --remote --command "SELECT COUNT(*) FROM $t"
293
+ done
294
+ ```
295
+
296
+ 结果和 Phase 5.1 的 `row-counts.txt` 对比。差异在几条以内可接受,大面积不一致回 Phase 7 重跑(INSERT OR IGNORE 幂等)。
297
+
298
+ ## Phase 8:Payment Kit — 写 wrangler.staging.json(10 min)
299
+
300
+ ```bash
301
+ cd <payment-kit-repo>/blocklets/core/cloudflare
302
+ cp wrangler.migration-test.json wrangler.staging.json
303
+ ```
304
+
305
+ 编辑 `wrangler.staging.json`,把关键字段改成 staging 值:
306
+
307
+ ```jsonc
308
+ {
309
+ "name": "payment-kit-staging",
310
+ "main": "dist/worker.js",
311
+ "compatibility_date": "2024-12-01",
312
+ "compatibility_flags": ["nodejs_compat"],
313
+ "placement": { "mode": "smart" },
314
+
315
+ "assets": {
316
+ "directory": "public",
317
+ "binding": "ASSETS",
318
+ "html_handling": "auto-trailing-slash",
319
+ "not_found_handling": "single-page-application"
320
+ },
321
+
322
+ "d1_databases": [
323
+ {
324
+ "binding": "DB",
325
+ "database_name": "payment-kit-staging",
326
+ "database_id": "<Phase 4.1 的 D1_ID>"
327
+ }
328
+ ],
329
+
330
+ "kv_namespaces": [
331
+ {
332
+ "binding": "DID_CONNECT_KV",
333
+ "id": "<Phase 4.2 的 KV_ID>"
334
+ }
335
+ ],
336
+
337
+ "services": [
338
+ {
339
+ "binding": "MEDIA_KIT",
340
+ "service": "<Phase 3 的 MEDIA_KIT_WORKER_NAME>"
341
+ },
342
+ {
343
+ "binding": "AUTH_SERVICE",
344
+ "service": "<TODO: STAGING_BLOCKLET_SERVICE_WORKER_NAME>",
345
+ "entrypoint": "BlockletServiceRPC"
346
+ }
347
+ ],
348
+
349
+ "vars": {
350
+ "APP_NAME": "Payment Kit Staging",
351
+ "APP_PID": "<Phase 0 的 APP_PID>",
352
+ "COMPONENT_DID": "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk",
353
+ "MEDIA_KIT_URL": "<Phase 3 的 MEDIA_KIT_URL>"
354
+ },
355
+
356
+ "queues": {
357
+ "producers": [
358
+ { "binding": "JOB_QUEUE", "queue": "payment-kit-staging-jobs" }
359
+ ],
360
+ "consumers": [
361
+ {
362
+ "queue": "payment-kit-staging-jobs",
363
+ "max_batch_size": 10,
364
+ "max_batch_timeout": 5,
365
+ "max_retries": 3,
366
+ "dead_letter_queue": "payment-kit-staging-jobs-dlq"
367
+ }
368
+ ]
369
+ },
370
+
371
+ "triggers": {
372
+ "crons": ["* * * * *"]
373
+ }
374
+ }
375
+ ```
376
+
377
+ ## Phase 9:Payment Kit — 设置 Secrets(5 min)
378
+
379
+ ```bash
380
+ cd <payment-kit-repo>/blocklets/core/cloudflare
381
+
382
+ wrangler secret put APP_SK --config wrangler.staging.json
383
+ # 粘贴 Phase 0 导出的 APP_SK hex
384
+ ```
385
+
386
+ 只需要设 `APP_SK`。`STRIPE_WEBHOOK_SECRET` 在 Phase 11 新建 Stripe endpoint 后再设;Stripe API key 走 DB + EK 解密,不设 env。
387
+
388
+ ## Phase 10:Payment Kit — 构建 + 部署(15 min)
389
+
390
+ ```bash
391
+ cd <payment-kit-repo>/blocklets/core
392
+
393
+ # 10.1 前端 SPA
394
+ npx vite build --config cloudflare/vite.config.ts --outDir cloudflare/public
395
+
396
+ # 10.2 Worker 入口
397
+ cd cloudflare
398
+ node run-build.js
399
+ # 预期:dist/worker.js 约 8~9MB
400
+
401
+ # 10.3 部署
402
+ npx wrangler deploy --config wrangler.staging.json
403
+
404
+ # 记下部署后的 URL,形如:
405
+ # https://payment-kit-staging.<account>.workers.dev
406
+ ```
407
+
408
+ ### 10.4 首请求日志验证
409
+
410
+ **立即**开启日志流,然后触发首请求:
411
+
412
+ ```bash
413
+ # Terminal A
414
+ wrangler tail --config wrangler.staging.json --format pretty
415
+
416
+ # Terminal B
417
+ curl https://payment-kit-staging.<account>.workers.dev/__blocklet__.js
418
+ # 预期:返回 JS,包含 window.blocklet = { ... }
419
+
420
+ curl https://payment-kit-staging.<account>.workers.dev/api/checkout/sessions/status/test
421
+ # 预期:404 Checkout session not found(不是 500/503)
422
+ ```
423
+
424
+ **Terminal A 应当看到**:
425
+
426
+ ```
427
+ [Security] Initialized with EK from AUTH_SERVICE
428
+ ```
429
+
430
+ 如果看到以下任意一条 → **停止**排查:
431
+
432
+ | 日志 | 原因 | 处理 |
433
+ |---|---|---|
434
+ | `[Security] No APP_EK found for instance <APP_PID>` | Phase 2 registerApp 没注册成功或 EK 没生成 | 回 Phase 2 重新注册,或找 DID 团队确认 EK 状态 |
435
+ | `[Security] initFromAuthService failed: ...` | `AUTH_SERVICE` binding 错误或 RPC 不可用 | 检查 `wrangler.staging.json` 的 `services[].service` 和 `entrypoint` |
436
+ | `503 AUTH_SERVICE not configured` | `services` 配置缺失 | 检查 `wrangler.staging.json` |
437
+ | `500 no such table: customers` | Phase 6 schema 没应用 | 回 Phase 6 |
438
+
439
+ ## Phase 11:Stripe Webhook 切换(10 min)
440
+
441
+ ### 11.1 在 Stripe Dashboard 新建 webhook endpoint
442
+
443
+ 1. 登录 Stripe Dashboard(**test mode**)
444
+ 2. Developers → Webhooks → Add endpoint
445
+ 3. Endpoint URL: `https://payment-kit-staging.<account>.workers.dev/api/stripe/webhook`
446
+ 4. Events to send:勾选以下(或 `Select all events`)
447
+ - `payment_intent.succeeded`
448
+ - `payment_intent.payment_failed`
449
+ - `payment_intent.canceled`
450
+ - `invoice.payment_succeeded`
451
+ - `invoice.payment_failed`
452
+ - `invoice.finalized`
453
+ - `customer.subscription.created`
454
+ - `customer.subscription.updated`
455
+ - `customer.subscription.deleted`
456
+ - `checkout.session.completed`
457
+ 5. 创建后点击 "Reveal" 拿到 `whsec_...`
458
+
459
+ ### 11.2 用真 secret 覆盖
460
+
461
+ ```bash
462
+ wrangler secret put STRIPE_WEBHOOK_SECRET --config wrangler.staging.json
463
+ # 粘贴 Phase 11.1 拿到的真 whsec_...
464
+ ```
465
+
466
+ Worker 会在几秒内热更新 secret,不需要重新 deploy。
467
+
468
+ ### 11.3 Stripe 侧测试 webhook
469
+
470
+ 在 Stripe Dashboard 的新 endpoint 页面点 "Send test webhook",选 `payment_intent.succeeded`,发送。
471
+
472
+ **Terminal A (`wrangler tail`) 预期**:
473
+
474
+ ```
475
+ POST /api/stripe/webhook 200
476
+ ```
477
+
478
+ 如果是 `400 signature verification failed` → 说明 Phase 11.2 secret 放错了,重新 put 一次。
479
+
480
+ ## Phase 12:端到端验证(30 min)
481
+
482
+ ### 12.1 `__blocklet__.js` 检查
483
+
484
+ ```bash
485
+ curl -s https://payment-kit-staging.<account>.workers.dev/__blocklet__.js
486
+ ```
487
+
488
+ 确认返回的 JS 里:
489
+ - `appName: 'Payment Kit Staging'`
490
+ - `appPid: '<APP_PID>'`
491
+ - `cloudflareWorker: true`
492
+
493
+ ### 12.2 Admin UI 登录
494
+
495
+ 1. 浏览器访问 `https://payment-kit-staging.<account>.workers.dev/admin`
496
+ 2. 点击 "Connect DID" 或类似按钮
497
+ 3. 用 DID Wallet 扫码登录
498
+ 4. **期望**:能看到迁移过来的 customers / subscriptions / invoices 列表
499
+
500
+ ### 12.3 Checkout 流程(Stripe test mode)
501
+
502
+ 1. 打开前端创建一个 checkout session(用现有的测试商品)
503
+ 2. 走完支付流程,用测试卡 `4242 4242 4242 4242`(任意未来日期 + 任意 CVC)
504
+ 3. **验证**:
505
+ - 支付成功页面正常
506
+ - D1 里对应 checkout_session 的 `status='complete'`、`payment_status='paid'`
507
+ - invoice 被创建(`SELECT * FROM invoices WHERE checkout_session_id = '<cs_xxx>'` 返回 1 行)
508
+ - Stripe Dashboard 的 webhook logs 显示 200
509
+
510
+ ### 12.4 订阅续费验证(如果 staging 数据里有活跃订阅)
511
+
512
+ ```bash
513
+ # 检查 cron 触发器是否工作
514
+ wrangler tail --config wrangler.staging.json --format pretty
515
+ # 等 1~2 分钟,应该能看到定时任务日志
516
+ ```
517
+
518
+ ### 12.5 关键 SQL 抽查
519
+
520
+ ```bash
521
+ wrangler d1 execute payment-kit-staging --remote --command \
522
+ "SELECT COUNT(*) FROM customers; SELECT COUNT(*) FROM invoices WHERE status='paid'; SELECT COUNT(*) FROM subscriptions WHERE status='active';"
523
+ ```
524
+
525
+ 行数应当和 AWS 源端大致匹配(±少量差异可接受,取决于迁移期间是否有新写入)。
526
+
527
+ ## Phase 13:回滚方案
528
+
529
+ **如果 Phase 10~12 任何一步发现致命问题**:
530
+
531
+ 1. **停止切流**:staging 还在 `*.workers.dev`,AWS 源端仍然运行,无需回滚 DNS
532
+ 2. **保留 CF 资源**:D1 / KV / Queue 不删,便于复现问题
533
+ 3. **修复后重跑**:Phase 5~12 是幂等的(`INSERT OR IGNORE`),可以安全重跑
534
+ 4. **确认失败原因**:查 `wrangler tail` 日志 + 对比 AWS 源端行为
535
+
536
+ **不要删除**:
537
+ - AWS staging(源数据是唯一真相)
538
+ - staging blocklet-service(DID 团队资产)
539
+ - 已注册的 APP_PID(重新注册会生成新 EK,导致 AWS 上的 Stripe 加密数据无法解密)
540
+
541
+ ## 附录 A:常用命令速查
542
+
543
+ ```bash
544
+ # 实时日志
545
+ wrangler tail --config wrangler.staging.json --format pretty
546
+
547
+ # D1 任意查询
548
+ wrangler d1 execute payment-kit-staging --remote --command "<SQL>"
549
+
550
+ # 某表行数
551
+ wrangler d1 execute payment-kit-staging --remote --command "SELECT COUNT(*) FROM <table>"
552
+
553
+ # 列出所有 secrets
554
+ wrangler secret list --config wrangler.staging.json
555
+
556
+ # 重新部署(代码改动后)
557
+ npx vite build --config cloudflare/vite.config.ts --outDir cloudflare/public && \
558
+ cd cloudflare && node run-build.js && \
559
+ npx wrangler deploy --config wrangler.staging.json
560
+ ```
561
+
562
+ ## 附录 B:Phase 对应所需时间
563
+
564
+ | Phase | 内容 | 预计耗时 | 可并行 |
565
+ |---|---|---|---|
566
+ | 前置确认 | 6 项环境就绪检查 | 10 min | - |
567
+ | Phase 0 | AWS 信息收集 | 15 min | - |
568
+ | Phase 1 | 密钥派生验证 | 5 min | - |
569
+ | Phase 2 | registerApp | 20 min | 与 Phase 3 并行 |
570
+ | Phase 3 | Media Kit staging 部署 | 30 min~1 hr | 与 Phase 2 并行 |
571
+ | Phase 4 | CF 资源创建 | 10 min | - |
572
+ | Phase 5 | 数据导出 + 处理 | 30 min | - |
573
+ | Phase 6 | Schema 部署 | 5 min | - |
574
+ | Phase 7 | 数据导入 | 1~2 hr | - |
575
+ | Phase 8 | wrangler.staging.json | 10 min | - |
576
+ | Phase 9 | 设置 secrets | 5 min | - |
577
+ | Phase 10 | 构建 + 部署 | 15 min | - |
578
+ | Phase 11 | Stripe webhook | 10 min | - |
579
+ | Phase 12 | 端到端验证 | 30 min | - |
580
+ | **合计** | | **约 3.5~5 hr** | |
581
+
582
+ ## 附录 C:`<TODO:>` 占位清单
583
+
584
+ 执行本手册前,把以下值补齐:
585
+
586
+ | 占位符 | 值 | 责任人 |
587
+ |---|---|---|
588
+ | `<TODO: AWS_STAGING_HOST>` | AWS staging SSH 目标 | Shijun(运维给) |
589
+ | `<TODO: STAGING_BLOCKLET_SERVICE_WORKER_NAME>` | staging blocklet-service Worker 名 | DID 团队给 |
590
+ | `<TODO: STAGING_BLOCKLET_SERVICE_URL>` | staging blocklet-service admin URL | DID 团队给 |
591
+ | `<TODO: media-kit-blocklet-name>` | AWS 上 Media Kit 的 blocklet name | Shijun(SSH 后看) |
592
+ | `<your-account>` (所有出现处) | Cloudflare account 域名前缀 | `wrangler whoami` 拿到 |
593
+
594
+ ---
595
+
596
+ ## 相关文档
597
+
598
+ - `README.md` — CF Workers 部署架构和环境变量总览
599
+ - `MIGRATION-RUNBOOK.md` — 通用迁移 runbook(本手册是它的 staging 特化版)
600
+ - `MIGRATION-CHALLENGES.md` — 迁移过程中的设计决策和踩坑记录
601
+ - `blocklet/media-kit` 仓库的 `cloudflare/README.md` — Media Kit CF 部署指南
602
+ - DID 仓库 `planning/29-blocklet-migration-runbook.md` — blocklet-service 迁移参考