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.
- package/__blocklet__.js +37 -0
- package/api/ocap-1.30-subpath-shims.d.ts +35 -0
- package/api/src/crons/index.ts +10 -0
- package/api/src/crons/metering-subscription-detection.ts +12 -14
- package/api/src/crons/overdue-detection.ts +51 -74
- package/api/src/integrations/arcblock/nft.ts +6 -2
- package/api/src/integrations/arcblock/stake.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +4 -4
- package/api/src/integrations/blocklet/notification.ts +1 -1
- package/api/src/integrations/ethereum/tx.ts +29 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
- package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
- package/api/src/integrations/stripe/resource.ts +8 -0
- package/api/src/libs/audit.ts +32 -16
- package/api/src/libs/auth.ts +49 -2
- package/api/src/libs/chain-error.ts +31 -0
- package/api/src/libs/error.ts +15 -0
- package/api/src/libs/event.ts +42 -1
- package/api/src/libs/invoice.ts +69 -34
- package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
- package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
- package/api/src/libs/pagination.ts +14 -9
- package/api/src/libs/payment.ts +25 -10
- package/api/src/libs/session.ts +1 -1
- package/api/src/libs/timing.ts +35 -0
- package/api/src/libs/util.ts +16 -15
- package/api/src/libs/wallet-migration.ts +72 -53
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/credit-consume.ts +94 -12
- package/api/src/queues/credit-grant.ts +4 -0
- package/api/src/queues/event.ts +14 -2
- package/api/src/queues/invoice.ts +1 -0
- package/api/src/queues/payment.ts +83 -15
- package/api/src/queues/refund.ts +84 -71
- package/api/src/queues/subscription.ts +1 -0
- package/api/src/routes/checkout-sessions.ts +82 -43
- package/api/src/routes/connect/change-payment.ts +2 -0
- package/api/src/routes/connect/change-plan.ts +2 -0
- package/api/src/routes/connect/pay.ts +12 -3
- package/api/src/routes/connect/setup.ts +3 -1
- package/api/src/routes/connect/shared.ts +52 -39
- package/api/src/routes/connect/subscribe.ts +4 -1
- package/api/src/routes/credit-grants.ts +25 -17
- package/api/src/routes/donations.ts +2 -2
- package/api/src/routes/meter-events.ts +16 -6
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +1 -1
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/tax-rates.ts +1 -1
- package/api/src/store/models/customer.ts +23 -1
- package/api/src/store/models/payment-method.ts +4 -0
- package/api/src/store/models/price.ts +23 -14
- package/api/tests/libs/wallet-migration.spec.ts +4 -4
- package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
- package/api/tests/queues/credit-consume.spec.ts +8 -4
- package/api/tests/routes/credit-grants.spec.ts +1 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
- package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
- package/cloudflare/README.md +499 -0
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
- package/cloudflare/build.ts +151 -0
- package/cloudflare/did-connect-auth.ts +527 -0
- package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
- package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
- package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
- package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
- package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
- package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
- package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
- package/cloudflare/frontend-shims/js-sdk.ts +43 -0
- package/cloudflare/frontend-shims/mime-types.ts +46 -0
- package/cloudflare/frontend-shims/session.ts +24 -0
- package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
- package/cloudflare/index.html +40 -0
- package/cloudflare/migrate-to-d1.js +252 -0
- package/cloudflare/migrations/0001_initial_schema.sql +82 -0
- package/cloudflare/migrations/0002_indexes.sql +75 -0
- package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
- package/cloudflare/run-build.js +390 -0
- package/cloudflare/scripts/test-decrypt.js +102 -0
- package/cloudflare/shims/arcblock-ws.ts +20 -0
- package/cloudflare/shims/axios-http-adapter.ts +4 -0
- package/cloudflare/shims/axios-lite.ts +117 -0
- package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
- package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
- package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
- package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
- package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
- package/cloudflare/shims/cookie-parser.ts +3 -0
- package/cloudflare/shims/cors.ts +21 -0
- package/cloudflare/shims/cron.ts +189 -0
- package/cloudflare/shims/crypto-js-warn.ts +7 -0
- package/cloudflare/shims/did-space-js.ts +17 -0
- package/cloudflare/shims/did-space.ts +11 -0
- package/cloudflare/shims/error.ts +18 -0
- package/cloudflare/shims/express-compat/index.ts +80 -0
- package/cloudflare/shims/express-compat/types.ts +41 -0
- package/cloudflare/shims/fastq.ts +105 -0
- package/cloudflare/shims/lock.ts +115 -0
- package/cloudflare/shims/mime-types.ts +56 -0
- package/cloudflare/shims/nedb-storage.ts +9 -0
- package/cloudflare/shims/node-child-process.ts +9 -0
- package/cloudflare/shims/node-fs.ts +20 -0
- package/cloudflare/shims/node-http.ts +13 -0
- package/cloudflare/shims/node-https.ts +4 -0
- package/cloudflare/shims/node-misc.ts +15 -0
- package/cloudflare/shims/node-net.ts +8 -0
- package/cloudflare/shims/node-os.ts +14 -0
- package/cloudflare/shims/node-tty.ts +8 -0
- package/cloudflare/shims/node-zlib.ts +17 -0
- package/cloudflare/shims/noop.ts +26 -0
- package/cloudflare/shims/payment-vendor.ts +14 -0
- package/cloudflare/shims/querystring.ts +12 -0
- package/cloudflare/shims/queue.ts +585 -0
- package/cloudflare/shims/rolldown-runtime.ts +43 -0
- package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
- package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
- package/cloudflare/shims/sequelize-d1/index.ts +34 -0
- package/cloudflare/shims/sequelize-d1/model.ts +1157 -0
- package/cloudflare/shims/sequelize-d1/operators.ts +293 -0
- package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
- package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
- package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
- package/cloudflare/shims/sequelize-d1/types.ts +35 -0
- package/cloudflare/shims/stripe-cf.ts +29 -0
- package/cloudflare/shims/ws-lite.ts +103 -0
- package/cloudflare/shims/xss.ts +3 -0
- package/cloudflare/tests/shims/cron.spec.ts +210 -0
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
- package/cloudflare/vite.config.ts +162 -0
- package/cloudflare/worker.ts +1553 -0
- package/cloudflare/wrangler.json +63 -0
- package/cloudflare/wrangler.jsonc +69 -0
- package/cloudflare/wrangler.staging.json +66 -0
- package/cloudflare/wrangler.toml +28 -0
- package/jest.config.js +4 -12
- package/package.json +26 -22
- package/src/app.tsx +62 -4
- package/src/components/customer/link.tsx +9 -13
- package/src/components/customer/notification-preference.tsx +3 -2
- package/src/components/filter-toolbar.tsx +4 -0
- package/src/components/invoice/list.tsx +9 -1
- package/src/components/invoice-pdf/utils.ts +2 -1
- package/src/components/layout/admin.tsx +39 -5
- package/src/components/layout/user-cf.tsx +77 -0
- package/src/components/payment-intent/actions.tsx +23 -3
- package/src/components/safe-did-address.tsx +75 -0
- package/src/libs/patch-user-card.ts +25 -0
- package/src/libs/util.ts +5 -7
- package/src/pages/admin/billing/meter-events/index.tsx +4 -0
- package/src/pages/admin/customers/customers/detail.tsx +2 -2
- package/src/pages/admin/customers/customers/index.tsx +2 -2
- package/src/pages/admin/overview.tsx +3 -1
- package/src/pages/customer/subscription/detail.tsx +4 -4
- package/tsconfig.api.json +1 -6
- package/tsconfig.json +3 -4
- package/tsconfig.types.json +2 -1
- package/vite.config.ts +6 -1
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
# Payment Kit Migration Runbook — 从 Blocklet Server 迁移到 Cloudflare Workers
|
|
2
|
+
|
|
3
|
+
> Date: 2026-04-08
|
|
4
|
+
> Status: Draft
|
|
5
|
+
> Scope: 将运行在 Blocklet Server 上的 Payment Kit(含支付数据、Stripe 配置、订阅、发票等)迁移到 Cloudflare Workers
|
|
6
|
+
>
|
|
7
|
+
> 参考: [DID 仓库迁移 Runbook](https://github.com/ArcBlock/did/blob/master/planning/29-blocklet-migration-runbook.md)
|
|
8
|
+
>
|
|
9
|
+
> 前置文档:
|
|
10
|
+
> - [MIGRATION-CHALLENGES.md](./MIGRATION-CHALLENGES.md) — 平台限制与支付链路挑战
|
|
11
|
+
> - [README.md](./README.md) — CF Workers 部署指南
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 前提条件
|
|
16
|
+
|
|
17
|
+
- Cloudflare 账号已创建,`wrangler` CLI 已安装
|
|
18
|
+
- 有 Blocklet Server 的 SSH 访问权限或数据库文件访问权限
|
|
19
|
+
- **AUTH_SERVICE (blocklet-service)** 已按 DID 仓库 Runbook 完成迁移部署
|
|
20
|
+
- **MEDIA_KIT** Worker 已部署
|
|
21
|
+
- Payment Kit CF Workers 代码已构建可部署
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 总览
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Phase 0: 准备 (30 min)
|
|
29
|
+
├── 收集信息
|
|
30
|
+
└── 创建 Cloudflare 资源
|
|
31
|
+
|
|
32
|
+
Phase 1: 密钥导出与验证 (1 hr)
|
|
33
|
+
├── 导出 APP_SK(兼作 DID 签名 + AES 加密密钥)
|
|
34
|
+
├── 验证 DID 派生一致性
|
|
35
|
+
└── 导出/确认 Stripe 密钥
|
|
36
|
+
|
|
37
|
+
Phase 2: 数据导出 (1 hr)
|
|
38
|
+
├── 38 张业务表的 SQLite 导出
|
|
39
|
+
└── 行数统计与完整性校验
|
|
40
|
+
|
|
41
|
+
Phase 3: 数据转换 (1 hr)
|
|
42
|
+
├── Stripe 加密密钥处理
|
|
43
|
+
├── 内网地址替换
|
|
44
|
+
└── exchange_rate_providers URL 修正
|
|
45
|
+
|
|
46
|
+
Phase 4: Schema 部署 (15 min)
|
|
47
|
+
└── wrangler d1 migrations apply
|
|
48
|
+
|
|
49
|
+
Phase 5: 数据导入 (1-2 hr)
|
|
50
|
+
├── 使用 migration-sql/ 分片文件逐表导入
|
|
51
|
+
└── 大表分片导入(credit_transactions、webhook_attempts 等)
|
|
52
|
+
|
|
53
|
+
Phase 6: 验证 (2 hr)
|
|
54
|
+
├── Checkout 流程验证(Stripe + 链上支付)
|
|
55
|
+
├── Stripe Webhook 验证
|
|
56
|
+
├── 订阅续费验证
|
|
57
|
+
└── Admin 管理面板验证
|
|
58
|
+
|
|
59
|
+
Phase 7: DNS 切换 (15 min)
|
|
60
|
+
└── 域名指向 CF Worker
|
|
61
|
+
|
|
62
|
+
Total: ~6-8 hours
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Phase 0: 准备
|
|
68
|
+
|
|
69
|
+
### 0.1 收集 Blocklet 信息
|
|
70
|
+
|
|
71
|
+
SSH 到 Blocklet Server:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 1. 确认 Payment Kit 的 DID 和 PID
|
|
75
|
+
echo "APP_DID: $BLOCKLET_APP_ID"
|
|
76
|
+
echo "APP_PID: $BLOCKLET_APP_PID"
|
|
77
|
+
|
|
78
|
+
# 2. 确认数据目录和数据库路径
|
|
79
|
+
echo "DATA_DIR: $BLOCKLET_DATA_DIR"
|
|
80
|
+
# Payment Kit 的数据库通常在: {data-dir}/payment.db 或 {data-dir}/data.db
|
|
81
|
+
|
|
82
|
+
# 3. 确认域名
|
|
83
|
+
echo "APP_URL: $BLOCKLET_APP_URL"
|
|
84
|
+
|
|
85
|
+
# 4. 确认 Stripe 是否启用
|
|
86
|
+
# 查看 payment_methods 表是否有 type='stripe' 的记录
|
|
87
|
+
|
|
88
|
+
# 5. 统计数据量(决定迁移时间窗口)
|
|
89
|
+
PAYMENT_DB="$BLOCKLET_DATA_DIR/data.db"
|
|
90
|
+
sqlite3 $PAYMENT_DB "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'" \
|
|
91
|
+
| while read t; do echo "$t: $(sqlite3 $PAYMENT_DB "SELECT count(*) FROM $t")"; done
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 0.2 确认依赖 Workers 已部署
|
|
95
|
+
|
|
96
|
+
| Worker | 用途 | 验证方法 |
|
|
97
|
+
|--------|------|---------|
|
|
98
|
+
| `blocklet-service` | DID 认证、用户管理 | `curl https://blocklet-service.your.workers.dev/.well-known/service/api/did/session` |
|
|
99
|
+
| `media-kit` | 文件上传、图片处理 | `curl https://media-kit.your.workers.dev/` |
|
|
100
|
+
|
|
101
|
+
> **重要**: AUTH_SERVICE 必须先完成迁移(包括用户数据)。Payment Kit 的 customers 表通过 DID 关联用户,
|
|
102
|
+
> 如果 AUTH_SERVICE 没有对应用户数据,登录和权限验证会失败。
|
|
103
|
+
|
|
104
|
+
### 0.3 创建 Cloudflare 资源
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 1. D1 数据库
|
|
108
|
+
wrangler d1 create payment-kit-prod
|
|
109
|
+
# 记录 database_id,填入 wrangler.json
|
|
110
|
+
|
|
111
|
+
# 2. KV Namespace(DID Connect session 存储)
|
|
112
|
+
wrangler kv namespace create DID_CONNECT_KV
|
|
113
|
+
# 记录 id,填入 wrangler.json
|
|
114
|
+
|
|
115
|
+
# 3. CF Queue(任务队列)
|
|
116
|
+
wrangler queues create payment-kit-jobs
|
|
117
|
+
|
|
118
|
+
# 4. Dead Letter Queue(可选,用于失败任务追踪)
|
|
119
|
+
wrangler queues create payment-kit-jobs-dlq
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Phase 1: 密钥导出与验证
|
|
125
|
+
|
|
126
|
+
### 1.1 导出 APP_SK
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# 方法 A: 从环境变量(推荐)
|
|
130
|
+
ssh blocklet-server
|
|
131
|
+
echo $BLOCKLET_APP_SK
|
|
132
|
+
# 输出: hex 字符串
|
|
133
|
+
|
|
134
|
+
# 方法 B: 从 server.db
|
|
135
|
+
sqlite3 /path/to/server.db \
|
|
136
|
+
"SELECT appSk FROM blocklets WHERE appDid='$BLOCKLET_APP_ID'"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> **Payment Kit 特殊性**: APP_SK 在 Payment Kit 中有双重用途:
|
|
140
|
+
> 1. **DID 签名**: DID Connect 认证、链上交易签名
|
|
141
|
+
> 2. **AES 加密密钥**: Stripe 密钥通过 `AES(PBKDF2(APP_SK, BLOCKLET_DID))` 加密后存储在
|
|
142
|
+
> `payment_methods` 表的 `metadata` 字段中
|
|
143
|
+
>
|
|
144
|
+
> 如果 APP_SK 导出错误,不仅登录失败,所有 Stripe 加密配置也无法解密。
|
|
145
|
+
|
|
146
|
+
### 1.2 验证 DID 派生一致性
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
node -e "
|
|
150
|
+
const { fromSecretKey } = require('@ocap/wallet');
|
|
151
|
+
const { types } = require('@ocap/mcrypto');
|
|
152
|
+
const sk = '<EXPORTED_APP_SK>';
|
|
153
|
+
const wallet = fromSecretKey(sk, {
|
|
154
|
+
role: types.RoleType.ROLE_APPLICATION,
|
|
155
|
+
pk: types.KeyType.ED25519,
|
|
156
|
+
hash: types.HashType.SHA3,
|
|
157
|
+
});
|
|
158
|
+
console.log('Derived APP DID:', wallet.address);
|
|
159
|
+
console.log('Expected: ', '<BLOCKLET_APP_ID>');
|
|
160
|
+
console.log('Match:', wallet.address === '<BLOCKLET_APP_ID>');
|
|
161
|
+
"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**如果 DID 不匹配,STOP! 不要继续迁移。**
|
|
165
|
+
|
|
166
|
+
### 1.3 确认 Stripe 密钥
|
|
167
|
+
|
|
168
|
+
Payment Kit 的 Stripe secret key 加密存储在 `payment_methods.metadata`,用 `AES(PBKDF2(APP_EK, APP_PID))` 加密。CF Worker 首请求时通过 `AUTH_SERVICE.getAppEk(APP_PID)` 拿 EK 后自动解密,**不需要**在 wrangler secret 里单独设置 Stripe API key。
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 查看 Stripe 配置
|
|
172
|
+
sqlite3 $PAYMENT_DB \
|
|
173
|
+
"SELECT id, type, metadata FROM payment_methods WHERE type='stripe'"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
前提:复用原 `APP_PID`,且 blocklet-service 上该 PID 的 EK 与 Blocklet Server 一致。EK 不一致时整条链断,必须让 DID 团队修复 EK,而不是退回到 env 覆盖。
|
|
177
|
+
|
|
178
|
+
Stripe Webhook Secret 是另一件事:CF 部署会新建 webhook endpoint,Stripe 给的是新的 `whsec_...`,**必须**在 Phase 5 用 `wrangler secret put STRIPE_WEBHOOK_SECRET` 设置。
|
|
179
|
+
|
|
180
|
+
### 1.4 记录关键标识
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# 记录以下值,后续步骤需要用到:
|
|
184
|
+
cat << 'INFO'
|
|
185
|
+
APP_SK: <hex>
|
|
186
|
+
APP_DID: <zNK...>
|
|
187
|
+
APP_PID: <zNK...> (可能与 APP_DID 相同)
|
|
188
|
+
COMPONENT_DID: z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk (固定值)
|
|
189
|
+
STRIPE_WEBHOOK_SECRET: whsec_... (Phase 5 新建 endpoint 后才有,先留空)
|
|
190
|
+
APP_URL: https://your-domain.com
|
|
191
|
+
INFO
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Phase 2: 数据导出
|
|
197
|
+
|
|
198
|
+
### 2.1 全表导出
|
|
199
|
+
|
|
200
|
+
Payment Kit 共有 38 张业务表。已准备好 `migration-sql/` 目录,包含预生成的 per-table SQL 导出文件。
|
|
201
|
+
|
|
202
|
+
如果需要重新导出(数据有更新):
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
PAYMENT_DB="$BLOCKLET_DATA_DIR/data.db"
|
|
206
|
+
|
|
207
|
+
# 列出所有表和行数
|
|
208
|
+
sqlite3 $PAYMENT_DB "SELECT name FROM sqlite_master WHERE type='table' \
|
|
209
|
+
AND name NOT LIKE 'sqlite_%' AND name != 'SequelizeMeta'" \
|
|
210
|
+
| while read t; do echo "$t: $(sqlite3 $PAYMENT_DB "SELECT count(*) FROM $t")"; done
|
|
211
|
+
|
|
212
|
+
# 导出所有表为 INSERT 语句
|
|
213
|
+
for table in $(sqlite3 $PAYMENT_DB "SELECT name FROM sqlite_master WHERE type='table' \
|
|
214
|
+
AND name NOT LIKE 'sqlite_%' AND name != 'SequelizeMeta'"); do
|
|
215
|
+
echo "Exporting $table..."
|
|
216
|
+
sqlite3 $PAYMENT_DB ".mode insert $table" "SELECT * FROM $table" \
|
|
217
|
+
> "migration-sql/${table}.sql"
|
|
218
|
+
# 改为幂等 INSERT
|
|
219
|
+
sed -i '' 's/INSERT INTO /INSERT OR IGNORE INTO /g' "migration-sql/${table}.sql"
|
|
220
|
+
done
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 2.2 大表分片
|
|
224
|
+
|
|
225
|
+
D1 的单条 SQL 最大 100KB 限制,大表必须分片:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# 对大表进行分片(每片 500 行)
|
|
229
|
+
for table in credit_transactions invoice_items invoices meter_events \
|
|
230
|
+
payment_intents payment_stats price_quotes webhook_attempts; do
|
|
231
|
+
FILE="migration-sql/${table}.sql"
|
|
232
|
+
if [ -f "$FILE" ] && [ $(wc -l < "$FILE") -gt 500 ]; then
|
|
233
|
+
split -l 500 "$FILE" "migration-sql/${table}-part"
|
|
234
|
+
# 重命名分片文件
|
|
235
|
+
i=1
|
|
236
|
+
for part in migration-sql/${table}-part*; do
|
|
237
|
+
mv "$part" "migration-sql/${table}-part${i}.sql"
|
|
238
|
+
i=$((i+1))
|
|
239
|
+
done
|
|
240
|
+
echo "$table: split into $((i-1)) parts"
|
|
241
|
+
fi
|
|
242
|
+
done
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 2.3 完整的表清单(38 张)
|
|
246
|
+
|
|
247
|
+
| 序号 | 表名 | 典型行数 | 分片 | 说明 |
|
|
248
|
+
|------|------|---------|------|------|
|
|
249
|
+
| 001 | archive_locks | <10 | 否 | 归档锁 |
|
|
250
|
+
| 002 | archive_metadata | <100 | 否 | 归档元数据 |
|
|
251
|
+
| 003 | auto_recharge_configs | <100 | 否 | 自动充值配置 |
|
|
252
|
+
| 004 | checkout_sessions | ~1K | 否 | 结账会话 |
|
|
253
|
+
| 005 | coupons | <100 | 否 | 优惠券 |
|
|
254
|
+
| 006 | credit_grants | ~1K | 否 | 额度授予 |
|
|
255
|
+
| 007 | credit_transactions | **~10K+** | **7 片** | 额度交易记录 |
|
|
256
|
+
| 008 | customers | ~1K | 否 | 客户 |
|
|
257
|
+
| 009 | discounts | <100 | 否 | 折扣 |
|
|
258
|
+
| 010 | exchange_rate_providers | <10 | 否 | 汇率提供商 |
|
|
259
|
+
| 011 | invoice_items | **~5K+** | **6 片** | 发票行项目 |
|
|
260
|
+
| 012 | invoices | **~5K+** | **7 片** | 发票 |
|
|
261
|
+
| 013 | jobs | ~1K | 否 | 后台任务 |
|
|
262
|
+
| 014 | locks | <10 | 否 | 分布式锁 |
|
|
263
|
+
| 015 | meter_events | **~5K+** | **7 片** | 计量事件 |
|
|
264
|
+
| 016 | meters | <100 | 否 | 计量器 |
|
|
265
|
+
| 017 | payment_currencies | <100 | 否 | 支付货币配置 |
|
|
266
|
+
| 018 | payment_intents | ~2K | **2 片** | 支付意向 |
|
|
267
|
+
| 019 | payment_links | <100 | 否 | 支付链接 |
|
|
268
|
+
| 020 | payment_methods | <10 | 否 | 支付方式(含加密 Stripe 密钥) |
|
|
269
|
+
| 021 | payment_stats | ~3K | **5 片** | 支付统计 |
|
|
270
|
+
| 022 | payouts | <100 | 否 | 提现 |
|
|
271
|
+
| 023 | price_quotes | ~2K | **2 片** | 报价 |
|
|
272
|
+
| 024 | prices | ~100 | 否 | 价格 |
|
|
273
|
+
| 025 | pricing_tables | <100 | 否 | 定价表 |
|
|
274
|
+
| 026 | product_vendors | <100 | 否 | 产品供应商 |
|
|
275
|
+
| 027 | products | ~100 | 否 | 产品 |
|
|
276
|
+
| 028 | promotion_codes | <100 | 否 | 促销码 |
|
|
277
|
+
| 029 | refunds | <100 | 否 | 退款 |
|
|
278
|
+
| 030 | revenue_snapshots | <100 | 否 | 收入快照 |
|
|
279
|
+
| 031 | settings | <100 | 否 | 配置 |
|
|
280
|
+
| 032 | setup_intents | <100 | 否 | Setup Intent |
|
|
281
|
+
| 033 | subscription_items | ~500 | 否 | 订阅项 |
|
|
282
|
+
| 034 | subscriptions | ~500 | 否 | 订阅 |
|
|
283
|
+
| 035 | tax_rates | <100 | 否 | 税率 |
|
|
284
|
+
| 036 | usage_records | <100 | 否 | 用量记录 |
|
|
285
|
+
| 037 | webhook_attempts | **~20K+** | **38 片** | Webhook 投递记录 |
|
|
286
|
+
| 038 | webhook_endpoints | <10 | 否 | Webhook 端点 |
|
|
287
|
+
|
|
288
|
+
### 2.4 校验源/目标 Schema 兼容性
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# 对比 Blocklet Server SQLite 和 CF Worker D1 的表结构
|
|
292
|
+
PAYMENT_DB="$BLOCKLET_DATA_DIR/data.db"
|
|
293
|
+
|
|
294
|
+
for table in customers invoices subscriptions payment_methods checkout_sessions; do
|
|
295
|
+
echo "=== $table ==="
|
|
296
|
+
echo "Source columns:"
|
|
297
|
+
sqlite3 $PAYMENT_DB "PRAGMA table_info($table)" | cut -d'|' -f2
|
|
298
|
+
echo "Target columns (from migration SQL):"
|
|
299
|
+
# 对比 migrations/0001_initial_schema.sql 中的 CREATE TABLE
|
|
300
|
+
done
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
> **重要**: Blocklet Server SQLite 是 Sequelize `sync()` 自动建表(camelCase 列名),
|
|
304
|
+
> CF Worker D1 的 migration 可能使用 snake_case。确认列名映射关系。
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Phase 3: 数据转换
|
|
309
|
+
|
|
310
|
+
### 3.1 Stripe 加密密钥处理
|
|
311
|
+
|
|
312
|
+
`payment_methods` 表中 `type='stripe'` 的记录,`metadata` 字段包含加密的 Stripe secret key,加密方式 `AES(PBKDF2(APP_EK, APP_PID))`。**这些数据原样导入 D1 即可,不需要任何转换**。
|
|
313
|
+
|
|
314
|
+
CF Worker 首请求时的解密链路:
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
initFromAuthService(env)
|
|
318
|
+
→ AUTH_SERVICE.getAppEk(APP_PID)
|
|
319
|
+
→ PBKDF2(appEk, APP_PID, 256, 32, sha512) → _password
|
|
320
|
+
PaymentMethod.getStripeClient()
|
|
321
|
+
→ decrypt(encryptedKey, _password)
|
|
322
|
+
→ new Stripe(plaintextKey)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
由 `blocklets/core/cloudflare/shims/blocklet-sdk/security.ts` 实现,代码路径和 Blocklet Server 完全一致。
|
|
326
|
+
|
|
327
|
+
**前提条件**:目标 blocklet-service 上 `APP_PID` 对应的 EK 必须和原 Blocklet Server 一致。如果 EK 不一致,加密字段无法解密,必须在 blocklet-service 侧修复 EK,而不是退回到 env 覆盖。
|
|
328
|
+
|
|
329
|
+
### 3.2 内网地址替换
|
|
330
|
+
|
|
331
|
+
Blocklet Server 环境下,某些配置可能引用内网地址:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# 扫描所有导出 SQL 文件中的内网地址
|
|
335
|
+
grep -rn '192\.168\.\|localhost\|127\.0\.0\.1\|10\.0\.\|172\.1[6-9]\.\|172\.2[0-9]\.\|172\.3[01]\.' \
|
|
336
|
+
migration-sql/*.sql
|
|
337
|
+
|
|
338
|
+
# 常见需要替换的:
|
|
339
|
+
# - exchange_rate_providers 表的 API URL
|
|
340
|
+
# - webhook_endpoints 表的回调 URL
|
|
341
|
+
# - settings 表的内部配置
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### 3.3 exchange_rate_providers URL 修正
|
|
345
|
+
|
|
346
|
+
`exchange_rate_providers` 表可能包含 Blocklet Server 内部代理 URL:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# 检查
|
|
350
|
+
sqlite3 $PAYMENT_DB "SELECT id, name, api_url FROM exchange_rate_providers"
|
|
351
|
+
|
|
352
|
+
# 典型需要替换的:
|
|
353
|
+
# http://192.168.x.x:port/api/exchange-rate → https://api.coingecko.com/api/v3/...
|
|
354
|
+
# http://localhost:port/... → 公网 API 端点
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
修改导出的 SQL 文件:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
sed -i '' 's|http://192\.168\.[0-9.]*:[0-9]*/|https://public-api-endpoint.com/|g' \
|
|
361
|
+
migration-sql/010-exchange_rate_providers.sql
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 3.4 webhook_endpoints URL 修正
|
|
365
|
+
|
|
366
|
+
如果有自定义 webhook endpoint 指向旧域名或内网:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
sqlite3 $PAYMENT_DB "SELECT id, url FROM webhook_endpoints"
|
|
370
|
+
# 确认所有 URL 在 CF Workers 环境下可达
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### 3.5 DID Connect tokens 表
|
|
374
|
+
|
|
375
|
+
CF Worker 使用 `_did_connect_tokens` 表(存在 D1 中)而非 KV 存储 DID Connect session,
|
|
376
|
+
以保证强一致性。该表由 schema migration 自动创建,无需从 Blocklet Server 迁移历史 token 数据
|
|
377
|
+
(用户会重新登录)。
|
|
378
|
+
|
|
379
|
+
> 注: wrangler.json 中仍保留了 `DID_CONNECT_KV` 配置,但实际 token 已迁移到 D1。
|
|
380
|
+
> KV 可用于其他缓存用途。
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Phase 4: Schema 部署
|
|
385
|
+
|
|
386
|
+
### 4.1 应用 D1 Schema Migration
|
|
387
|
+
|
|
388
|
+
Payment Kit 使用 `migrations_dir` 配置,包含 3 个 migration 文件:
|
|
389
|
+
|
|
390
|
+
| 文件 | 内容 |
|
|
391
|
+
|------|------|
|
|
392
|
+
| `0001_initial_schema.sql` | 40 张表的 CREATE TABLE(含 `_did_connect_tokens`) |
|
|
393
|
+
| `0002_indexes.sql` | 72 个索引 |
|
|
394
|
+
| `0003_locks_and_constraints.sql` | 锁表初始数据和约束 |
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
cd blocklets/core/cloudflare
|
|
398
|
+
|
|
399
|
+
# 使用 wrangler d1 migrations apply(推荐,会记录已执行的 migration)
|
|
400
|
+
wrangler d1 migrations apply payment-kit-prod --remote
|
|
401
|
+
|
|
402
|
+
# 或逐文件手动执行
|
|
403
|
+
for f in migrations/0001_initial_schema.sql migrations/0002_indexes.sql migrations/0003_locks_and_constraints.sql; do
|
|
404
|
+
echo "Applying $(basename $f)..."
|
|
405
|
+
wrangler d1 execute payment-kit-prod --remote --file="$f"
|
|
406
|
+
done
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 4.2 验证表已创建
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
wrangler d1 execute payment-kit-prod --remote \
|
|
413
|
+
--command "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
|
414
|
+
|
|
415
|
+
# 预期: 40 张表(38 业务表 + _did_connect_tokens + d1_migrations)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Phase 5: 数据导入
|
|
421
|
+
|
|
422
|
+
### 5.1 导入策略
|
|
423
|
+
|
|
424
|
+
使用 `migration-sql/` 目录下的预生成文件,按序号逐表导入。大表使用分片文件。
|
|
425
|
+
|
|
426
|
+
**导入顺序很重要** — 有外键依赖的表需要先导入被依赖方:
|
|
427
|
+
|
|
428
|
+
```
|
|
429
|
+
1. 基础表: customers, products, prices, meters, coupons, tax_rates
|
|
430
|
+
2. 配置表: payment_methods, payment_currencies, exchange_rate_providers, settings
|
|
431
|
+
3. 业务表: subscriptions, invoices, checkout_sessions, ...
|
|
432
|
+
4. 交易表: credit_transactions, payment_intents, payment_stats, ...
|
|
433
|
+
5. 日志表: webhook_attempts, jobs, meter_events
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### 5.2 执行导入
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
cd blocklets/core/cloudflare
|
|
440
|
+
|
|
441
|
+
# 小表: 直接导入
|
|
442
|
+
for f in migration-sql/001-*.sql migration-sql/002-*.sql migration-sql/003-*.sql \
|
|
443
|
+
migration-sql/005-*.sql migration-sql/008-*.sql migration-sql/009-*.sql \
|
|
444
|
+
migration-sql/010-*.sql migration-sql/014-*.sql migration-sql/016-*.sql \
|
|
445
|
+
migration-sql/017-*.sql migration-sql/019-*.sql migration-sql/020-*.sql \
|
|
446
|
+
migration-sql/022-*.sql migration-sql/024-*.sql migration-sql/025-*.sql \
|
|
447
|
+
migration-sql/026-*.sql migration-sql/027-*.sql migration-sql/028-*.sql \
|
|
448
|
+
migration-sql/029-*.sql migration-sql/030-*.sql migration-sql/031-*.sql \
|
|
449
|
+
migration-sql/032-*.sql migration-sql/033-*.sql migration-sql/034-*.sql \
|
|
450
|
+
migration-sql/035-*.sql migration-sql/036-*.sql migration-sql/038-*.sql; do
|
|
451
|
+
if [ -f "$f" ] && ! echo "$f" | grep -q "part"; then
|
|
452
|
+
echo "Importing $(basename $f)..."
|
|
453
|
+
wrangler d1 execute payment-kit-prod --remote --file="$f"
|
|
454
|
+
fi
|
|
455
|
+
done
|
|
456
|
+
|
|
457
|
+
# 大表分片导入 — 逐片执行
|
|
458
|
+
for table in credit_transactions invoice_items invoices meter_events \
|
|
459
|
+
payment_intents payment_stats price_quotes webhook_attempts; do
|
|
460
|
+
for part in migration-sql/*${table}-part*.sql; do
|
|
461
|
+
if [ -f "$part" ]; then
|
|
462
|
+
echo "Importing $(basename $part)..."
|
|
463
|
+
wrangler d1 execute payment-kit-prod --remote --file="$part"
|
|
464
|
+
fi
|
|
465
|
+
done
|
|
466
|
+
done
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
> **注意**: 如果某个分片导入失败(SQL 语法错误或约束冲突),检查错误后可重新执行
|
|
470
|
+
> (所有 INSERT 都使用 `INSERT OR IGNORE`,幂等安全)。
|
|
471
|
+
|
|
472
|
+
### 5.3 验证导入结果
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
# 逐表对比源和目标行数
|
|
476
|
+
TABLES="archive_locks archive_metadata auto_recharge_configs checkout_sessions \
|
|
477
|
+
coupons credit_grants credit_transactions customers discounts \
|
|
478
|
+
exchange_rate_providers invoice_items invoices jobs locks meter_events \
|
|
479
|
+
meters payment_currencies payment_intents payment_links payment_methods \
|
|
480
|
+
payment_stats payouts price_quotes prices pricing_tables product_vendors \
|
|
481
|
+
products promotion_codes refunds revenue_snapshots settings setup_intents \
|
|
482
|
+
subscription_items subscriptions tax_rates usage_records webhook_attempts \
|
|
483
|
+
webhook_endpoints"
|
|
484
|
+
|
|
485
|
+
for table in $TABLES; do
|
|
486
|
+
count=$(wrangler d1 execute payment-kit-prod --remote \
|
|
487
|
+
--command "SELECT count(*) as c FROM $table" --json 2>/dev/null \
|
|
488
|
+
| python3 -c "import sys,json; print(json.loads(sys.stdin.read())[0]['results'][0]['c'])" 2>/dev/null)
|
|
489
|
+
echo "$table: $count"
|
|
490
|
+
done
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
将上述输出与 Phase 2.1 的源数据行数对比,确认一致。
|
|
494
|
+
|
|
495
|
+
### 5.4 设置 Secrets
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
# 必需: 应用私钥
|
|
499
|
+
wrangler secret put APP_SK
|
|
500
|
+
# 粘贴 Phase 1.1 导出的 hex 字符串
|
|
501
|
+
|
|
502
|
+
# 必需(启用 Stripe 时): 新 webhook endpoint 的 signing secret
|
|
503
|
+
wrangler secret put STRIPE_WEBHOOK_SECRET
|
|
504
|
+
# 粘贴 whsec_...
|
|
505
|
+
# 注意:这是 CF Worker 新建 webhook endpoint 后 Stripe Dashboard 给的新 secret,
|
|
506
|
+
# 不是 Blocklet Server 原 endpoint 的旧 secret。Stripe API key 不需要设置,
|
|
507
|
+
# 走 DB + EK 解密路径(见 Phase 3.1)。
|
|
508
|
+
|
|
509
|
+
# 可选: 自定义域名
|
|
510
|
+
wrangler secret put APP_URL
|
|
511
|
+
# 粘贴 https://your-domain.com
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### 5.5 部署 Worker
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
cd blocklets/core
|
|
518
|
+
|
|
519
|
+
# 1. 构建前端
|
|
520
|
+
npx vite build --config cloudflare/vite.config.ts --outDir cloudflare/public
|
|
521
|
+
|
|
522
|
+
# 2. 构建 Worker
|
|
523
|
+
cd cloudflare
|
|
524
|
+
node run-build.js
|
|
525
|
+
|
|
526
|
+
# 3. 部署
|
|
527
|
+
npx wrangler deploy --config wrangler.json
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Phase 6: 验证
|
|
533
|
+
|
|
534
|
+
### 6.1 基础验证
|
|
535
|
+
|
|
536
|
+
```bash
|
|
537
|
+
WORKER_URL="https://payment-kit.your.workers.dev"
|
|
538
|
+
|
|
539
|
+
# __blocklet__.js 输出
|
|
540
|
+
curl -s "$WORKER_URL/__blocklet__.js?type=json" | jq '{
|
|
541
|
+
appId, appPid, appPk, appName
|
|
542
|
+
}'
|
|
543
|
+
|
|
544
|
+
# 确认:
|
|
545
|
+
# - appId 与原 Blocklet Server 一致
|
|
546
|
+
# - appPk 与原 Blocklet Server 一致(base58 公钥)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### 6.2 Admin 面板验证
|
|
550
|
+
|
|
551
|
+
```
|
|
552
|
+
https://payment-kit.your.workers.dev/admin/
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
- [ ] 能登录(通过 AUTH_SERVICE 的 DID Connect)
|
|
556
|
+
- [ ] Products 列表显示迁移的产品
|
|
557
|
+
- [ ] Prices 列表显示迁移的价格
|
|
558
|
+
- [ ] Customers 列表显示迁移的客户
|
|
559
|
+
- [ ] Subscriptions 列表显示迁移的订阅
|
|
560
|
+
- [ ] Invoices 列表显示迁移的发票
|
|
561
|
+
- [ ] Payment Methods 配置正确(Stripe 连接状态)
|
|
562
|
+
- [ ] Payment Currencies 配置正确
|
|
563
|
+
|
|
564
|
+
### 6.3 Checkout 流程验证
|
|
565
|
+
|
|
566
|
+
| 流程 | 测试步骤 | 预期结果 |
|
|
567
|
+
|------|---------|---------|
|
|
568
|
+
| **Stripe 一次性支付** | 创建 checkout session → 填表 → Stripe 支付 → 回调 | 发票状态变为 paid |
|
|
569
|
+
| **链上支付 (TBA)** | 创建 checkout session → DID Connect → 链上转账 | 发票状态变为 paid |
|
|
570
|
+
| **订阅 (Stripe)** | 选择订阅产品 → Stripe 支付 → 创建订阅 | 订阅状态 active |
|
|
571
|
+
| **促销码** | 输入促销码 → 金额折扣 | 折扣正确计算 |
|
|
572
|
+
| **动态定价** | 切换支付货币 → 汇率获取 | 汇率正常显示 |
|
|
573
|
+
|
|
574
|
+
### 6.4 Stripe Webhook 验证
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
# 1. 在 Stripe Dashboard 中添加新的 Webhook Endpoint
|
|
578
|
+
# URL: https://payment-kit.your.workers.dev/api/stripe/webhooks
|
|
579
|
+
# Events: payment_intent.succeeded, invoice.paid, customer.subscription.updated, ...
|
|
580
|
+
|
|
581
|
+
# 2. 发送测试事件
|
|
582
|
+
stripe trigger payment_intent.succeeded --webhook-endpoint=<endpoint_id>
|
|
583
|
+
|
|
584
|
+
# 3. 检查 D1 中的处理结果
|
|
585
|
+
wrangler d1 execute payment-kit-prod --remote \
|
|
586
|
+
--command "SELECT id, event_type, status FROM webhook_attempts ORDER BY created_at DESC LIMIT 5"
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
> **重要**: Stripe Webhook 签名验证使用 `STRIPE_WEBHOOK_SECRET`。新的 CF Worker endpoint
|
|
590
|
+
> 会有新的 signing secret,必须在 Stripe Dashboard 创建新的 webhook endpoint 并获取新的 secret。
|
|
591
|
+
> 旧的 Blocklet Server webhook endpoint 保留(回滚用),但可以先禁用。
|
|
592
|
+
|
|
593
|
+
### 6.5 订阅续费验证
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# 检查 cron trigger 是否正常工作
|
|
597
|
+
# wrangler.json 配置了 "*/5 * * * *"(每 5 分钟)
|
|
598
|
+
# 观察 D1 中的 jobs 表是否有新的续费任务
|
|
599
|
+
|
|
600
|
+
wrangler d1 execute payment-kit-prod --remote \
|
|
601
|
+
--command "SELECT id, type, status, next_run_at FROM jobs WHERE type LIKE '%renewal%' ORDER BY next_run_at DESC LIMIT 5"
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### 6.6 Credit 系统验证
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
# 检查 credit_grants 和 credit_transactions
|
|
608
|
+
wrangler d1 execute payment-kit-prod --remote \
|
|
609
|
+
--command "SELECT customer_id, SUM(amount) as total FROM credit_transactions GROUP BY customer_id LIMIT 10"
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### 6.7 Service Binding 验证
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
# 验证 AUTH_SERVICE binding 正常
|
|
616
|
+
curl -s "$WORKER_URL/.well-known/service/api/did/session" \
|
|
617
|
+
-H "Cookie: login_token=<valid_jwt>"
|
|
618
|
+
# 预期: 返回用户 session 信息
|
|
619
|
+
|
|
620
|
+
# 验证 MEDIA_KIT binding 正常
|
|
621
|
+
# 上传一个文件测试(通过 admin 面板)
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## Phase 7: DNS 切换
|
|
627
|
+
|
|
628
|
+
### 7.1 切换前检查
|
|
629
|
+
|
|
630
|
+
- [ ] Phase 6 所有验证项通过
|
|
631
|
+
- [ ] Stripe Webhook 新 endpoint 已配置并测试
|
|
632
|
+
- [ ] 监控告警已配置
|
|
633
|
+
- [ ] 选择低流量时段执行
|
|
634
|
+
|
|
635
|
+
### 7.2 执行 DNS 切换
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
# 方法 A: Cloudflare Custom Domains(推荐)
|
|
639
|
+
wrangler deploy --route your-domain.com/*
|
|
640
|
+
|
|
641
|
+
# 方法 B: 在 Cloudflare DNS 中手动操作
|
|
642
|
+
# 将原域名的 A/CNAME 记录改为指向 CF Worker
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### 7.3 切换后操作
|
|
646
|
+
|
|
647
|
+
1. **Stripe Webhook**: 确认新 endpoint 正常接收事件,禁用旧的 Blocklet Server endpoint
|
|
648
|
+
2. **原 Blocklet Server**: 停止 Payment Kit blocklet(但保留数据,不删除)
|
|
649
|
+
3. **监控**: 观察 24 小时
|
|
650
|
+
- Checkout 成功率
|
|
651
|
+
- Webhook 处理延迟
|
|
652
|
+
- 订阅续费是否正常触发
|
|
653
|
+
- Error rate(CF Worker Logs)
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## 回滚方案
|
|
658
|
+
|
|
659
|
+
### 触发条件
|
|
660
|
+
|
|
661
|
+
- Checkout 支付流程完全不可用
|
|
662
|
+
- Stripe Webhook 处理持续失败
|
|
663
|
+
- 订阅续费系统性失败
|
|
664
|
+
|
|
665
|
+
### 回滚步骤
|
|
666
|
+
|
|
667
|
+
1. **DNS 回滚**: 将域名指回原 Blocklet Server (5 min)
|
|
668
|
+
2. **Stripe Webhook 回滚**: 重新启用旧 endpoint,禁用新 endpoint
|
|
669
|
+
3. **原 Blocklet Server 数据完整**: 未删除任何原始数据
|
|
670
|
+
4. **增量数据处理**: 迁移窗口期间在 CF Worker 上产生的新数据(新订单、新订阅)需要手动同步回 Blocklet Server
|
|
671
|
+
|
|
672
|
+
### 最小化回滚风险
|
|
673
|
+
|
|
674
|
+
- 在低流量时段执行迁移
|
|
675
|
+
- 迁移前冻结 Blocklet Server 上的写操作(设为只读或停止接受新订单)
|
|
676
|
+
- 保留原 Blocklet Server 至少 30 天
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## Checklist 总览
|
|
681
|
+
|
|
682
|
+
```
|
|
683
|
+
准备:
|
|
684
|
+
[ ] 收集 Payment Kit DID, data dir, domain, 数据量信息
|
|
685
|
+
[ ] 确认 AUTH_SERVICE (blocklet-service) 已迁移部署
|
|
686
|
+
[ ] 确认 MEDIA_KIT Worker 已部署
|
|
687
|
+
[ ] 创建 D1 database + KV namespace + Queue
|
|
688
|
+
[ ] 记录所有 resource ID
|
|
689
|
+
|
|
690
|
+
密钥:
|
|
691
|
+
[ ] 导出 APP_SK
|
|
692
|
+
[ ] DID 派生验证通过
|
|
693
|
+
[ ] 确认 Stripe 密钥处理策略(环境变量 vs 数据库解密)
|
|
694
|
+
[ ] 从 Stripe Dashboard 获取明文密钥(策略 A)
|
|
695
|
+
|
|
696
|
+
数据导出:
|
|
697
|
+
[ ] 38 张表全部导出为 SQL
|
|
698
|
+
[ ] 大表分片完成(credit_transactions、webhook_attempts 等)
|
|
699
|
+
[ ] 行数统计记录(用于导入后对比)
|
|
700
|
+
[ ] 校验源/目标 Schema 列名一致性
|
|
701
|
+
|
|
702
|
+
数据转换:
|
|
703
|
+
[ ] exchange_rate_providers URL 内网地址已替换
|
|
704
|
+
[ ] webhook_endpoints URL 已检查
|
|
705
|
+
[ ] settings 表内网引用已清理
|
|
706
|
+
[ ] payment_methods 表 Stripe 加密数据处理策略已确认
|
|
707
|
+
|
|
708
|
+
Schema 部署:
|
|
709
|
+
[ ] D1 migrations apply 成功(3 个文件)
|
|
710
|
+
[ ] 40 张表已创建确认
|
|
711
|
+
|
|
712
|
+
数据导入:
|
|
713
|
+
[ ] 小表导入完成
|
|
714
|
+
[ ] 大表分片导入完成(含重试)
|
|
715
|
+
[ ] 行数验证: 源 vs 目标一致
|
|
716
|
+
[ ] Secrets 设置: APP_SK, STRIPE_WEBHOOK_SECRET
|
|
717
|
+
|
|
718
|
+
部署:
|
|
719
|
+
[ ] 前端构建
|
|
720
|
+
[ ] Worker 构建
|
|
721
|
+
[ ] Worker 部署成功
|
|
722
|
+
|
|
723
|
+
验证:
|
|
724
|
+
[ ] __blocklet__.js 输出正确(appId, appPk 匹配)
|
|
725
|
+
[ ] Admin 面板登录正常
|
|
726
|
+
[ ] Products/Prices/Customers/Subscriptions 数据完整
|
|
727
|
+
[ ] Checkout → Stripe 支付端到端通过
|
|
728
|
+
[ ] Checkout → 链上支付端到端通过
|
|
729
|
+
[ ] Stripe Webhook 接收并处理成功
|
|
730
|
+
[ ] 订阅续费 cron 正常触发
|
|
731
|
+
[ ] Credit 余额计算正确
|
|
732
|
+
[ ] AUTH_SERVICE binding 正常
|
|
733
|
+
[ ] MEDIA_KIT binding 正常
|
|
734
|
+
|
|
735
|
+
切换:
|
|
736
|
+
[ ] Stripe Dashboard 新 webhook endpoint 已创建
|
|
737
|
+
[ ] DNS 切换完成
|
|
738
|
+
[ ] 旧 Stripe webhook endpoint 已禁用
|
|
739
|
+
[ ] 原 Blocklet Server 已停止(保留数据)
|
|
740
|
+
[ ] 监控 24h 无异常
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## 实战经验(持续更新)
|
|
746
|
+
|
|
747
|
+
### 1. APP_SK 双用途容易遗漏
|
|
748
|
+
|
|
749
|
+
Payment Kit 的 APP_SK 不仅用于 DID 签名,还用于 Stripe 密钥加密。
|
|
750
|
+
如果只验证了 DID 派生,没有验证 Stripe 解密,可能在 checkout 支付时才发现问题。
|
|
751
|
+
|
|
752
|
+
**规则**: Phase 1 验证时,除了 DID 派生,还要验证能否用 APP_SK 解密 payment_methods 中的 Stripe 密钥
|
|
753
|
+
(或确认使用环境变量覆盖策略)。
|
|
754
|
+
|
|
755
|
+
### 2. Webhook Signing Secret 是 endpoint 级别的
|
|
756
|
+
|
|
757
|
+
每个 Stripe Webhook endpoint 有独立的 signing secret。
|
|
758
|
+
CF Worker 的新 URL 需要在 Stripe Dashboard 创建新的 endpoint,获取新的 `whsec_` 密钥。
|
|
759
|
+
不能复用 Blocklet Server 的 `STRIPE_WEBHOOK_SECRET`(除非把旧 endpoint 的 URL 改成新的)。
|
|
760
|
+
|
|
761
|
+
**规则**: 要么在 Stripe Dashboard 修改旧 endpoint 的 URL(保持 secret 不变),
|
|
762
|
+
要么创建新 endpoint + 新 secret + 更新 `STRIPE_WEBHOOK_SECRET` 环境变量。
|
|
763
|
+
|
|
764
|
+
### 3. D1 单条 SQL 100KB 限制
|
|
765
|
+
|
|
766
|
+
webhook_attempts 表数据量大(38 个分片),每个分片文件不能超过 100KB。
|
|
767
|
+
如果单行数据很大(如 webhook payload),可能需要更小的分片粒度。
|
|
768
|
+
|
|
769
|
+
**规则**: 分片后检查每个文件大小: `ls -la migration-sql/*-part*.sql | awk '{print $5, $9}' | sort -rn | head`
|
|
770
|
+
|
|
771
|
+
### 4. Queue 任务不迁移
|
|
772
|
+
|
|
773
|
+
Blocklet Server 上未完成的 queue 任务(jobs 表中 `status='pending'`)不需要迁移。
|
|
774
|
+
CF Worker 使用 CF Queues + Cron Trigger,重新调度即可。但要确保没有"正在执行中"的关键任务
|
|
775
|
+
(如 Stripe 退款中间状态)。
|
|
776
|
+
|
|
777
|
+
**规则**: 迁移前检查 `SELECT * FROM jobs WHERE status IN ('pending', 'processing')` 并等待完成。
|