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,499 @@
|
|
|
1
|
+
# Payment Kit — Cloudflare Workers 部署指南
|
|
2
|
+
|
|
3
|
+
> 本指南对应已经移除全部 mock 的版本(`8e0f092e` 之后)。所有身份验证、加密、签名、文件上传均走真实服务,不再支持本地 fake 凭据。
|
|
4
|
+
|
|
5
|
+
## 架构
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Payment Kit Worker
|
|
9
|
+
├── D1 Database # 业务数据(customers/invoices/subscriptions ...)
|
|
10
|
+
├── KV: DID_CONNECT_KV # DID Connect session 临时存储
|
|
11
|
+
├── CF Queue # 任务队列(fastq → CF Queue 适配)
|
|
12
|
+
├── Hyperdrive # (可选)连接 Postgres,仅在启用时使用
|
|
13
|
+
├── Service Binding
|
|
14
|
+
│ ├── AUTH_SERVICE → blocklet-service (BlockletServiceRPC)
|
|
15
|
+
│ └── MEDIA_KIT → media-kit
|
|
16
|
+
└── Assets # 前端 SPA(Vite 构建产物)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
关键组件:
|
|
20
|
+
|
|
21
|
+
- **Hono** 替代 Express(路由 + 中间件适配层)
|
|
22
|
+
- **Sequelize-D1 shim** 替代 SQLite,表结构自动 sync
|
|
23
|
+
- **CF Queue** 替代 fastq,加 D1-native cron fallback
|
|
24
|
+
- **wrapAsyncListener** 追踪 `EventEmitter` async listener 的 Promise,防止 CF runtime 提前回收
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## ⚠️ 必须先部署的前置服务
|
|
29
|
+
|
|
30
|
+
Payment Kit 自身**不做**任何身份验证、加密初始化、文件处理 —— 全部委托给依赖 Worker。**以下两个 Worker 必须先部署成功**,否则 Payment Kit 启动后 API 会 503 或无法加载 Stripe 密钥。
|
|
31
|
+
|
|
32
|
+
### 1. blocklet-service(即 DID Service)
|
|
33
|
+
|
|
34
|
+
Payment Kit 的身份来源,提供:
|
|
35
|
+
|
|
36
|
+
- `resolveIdentity(jwt, authHeader, appPid)` — 解析登录态,返回 `{ did, role, displayName, avatar }`
|
|
37
|
+
- `getAppEk(appPid)` — 提供 APP_EK(Stripe 密钥解密 + 其他敏感字段解密)
|
|
38
|
+
- `/__auth__/*` fetch fallback — DID Connect challenge / verify / token
|
|
39
|
+
- `component.call` 签名验证(通过 `X-Component-Sig` 回调 AUTH_SERVICE)
|
|
40
|
+
|
|
41
|
+
**要求**:
|
|
42
|
+
|
|
43
|
+
- 暴露 `BlockletServiceRPC` 作为 `entrypoint`
|
|
44
|
+
- 已注册 Payment Kit 的 `APP_PID`(拿到固定的 instance DID)
|
|
45
|
+
- EK 对应该 APP_PID 存在且未过期
|
|
46
|
+
|
|
47
|
+
**部署前置**:先完成 blocklet-service 的部署,拿到 service name(如 `blocklet-service`),并且确认 `resolveIdentity` / `getAppEk` RPC 方法可用。
|
|
48
|
+
|
|
49
|
+
### 2. media-kit
|
|
50
|
+
|
|
51
|
+
Payment Kit 的 upload/image CDN,用于:
|
|
52
|
+
|
|
53
|
+
- 商户 logo / 商品图片上传
|
|
54
|
+
- 发票附件、凭证下载
|
|
55
|
+
- 前端 `/api/uploads/*` 代理目标
|
|
56
|
+
|
|
57
|
+
**要求**:
|
|
58
|
+
|
|
59
|
+
- 暴露 HTTP endpoint(给 `MEDIA_KIT_URL` 用作 `__blocklet__.js` 的 `mediaKitUrl`)
|
|
60
|
+
- 同时导出 service binding `media-kit`(给 `MEDIA_KIT` binding 用,支持签名上传)
|
|
61
|
+
|
|
62
|
+
**部署前置**:先部署 media-kit 拿到 `workers.dev` URL 或自定义域名,填到 Payment Kit 的 `MEDIA_KIT_URL` 变量。
|
|
63
|
+
|
|
64
|
+
### 部署顺序
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
blocklet-service → 已存在,在其他项目里先部署好
|
|
68
|
+
│
|
|
69
|
+
├── 注册 Payment Kit APP_PID(得到 instance DID)
|
|
70
|
+
├── 生成/确认 APP_EK
|
|
71
|
+
└── 确认 BlockletServiceRPC 方法 resolveIdentity / getAppEk 已实现
|
|
72
|
+
↓
|
|
73
|
+
media-kit → 部署并拿到 URL
|
|
74
|
+
↓
|
|
75
|
+
Payment Kit Worker → 本指南涵盖的部分
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 前置 Cloudflare 资源
|
|
81
|
+
|
|
82
|
+
在 Payment Kit 部署前创建以下资源,把生成的 ID 填到 `wrangler.jsonc`:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# 1. D1 数据库
|
|
86
|
+
wrangler d1 create payment-kit-prod
|
|
87
|
+
# → 记下 database_id
|
|
88
|
+
|
|
89
|
+
# 2. KV Namespace(DID Connect session 存储)
|
|
90
|
+
wrangler kv namespace create DID_CONNECT_KV
|
|
91
|
+
# → 记下 id
|
|
92
|
+
|
|
93
|
+
# 3. CF Queue + DLQ(任务队列)
|
|
94
|
+
wrangler queues create payment-kit-jobs
|
|
95
|
+
wrangler queues create payment-kit-jobs-dlq
|
|
96
|
+
|
|
97
|
+
# 4.(可选)Hyperdrive,仅当需要连接外部 Postgres 时
|
|
98
|
+
wrangler hyperdrive create payment-kit-hyperdrive --connection-string "postgres://..."
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 环境变量完整清单
|
|
104
|
+
|
|
105
|
+
### 两类变量
|
|
106
|
+
|
|
107
|
+
- **`vars`**:非敏感,写在 `wrangler.jsonc` 里,部署时一起提交。
|
|
108
|
+
- **`secrets`**:敏感,用 `wrangler secret put` 单独设置,不进源码。
|
|
109
|
+
|
|
110
|
+
### 完整清单
|
|
111
|
+
|
|
112
|
+
| 变量 | 类型 | 必需 | 说明 | 示例 |
|
|
113
|
+
|------|------|------|------|------|
|
|
114
|
+
| `APP_NAME` | var | ✅ | 应用显示名,出现在 header + `__blocklet__.js` | `"Payment Kit"` |
|
|
115
|
+
| `APP_PID` | var | ✅ | Instance DID,blocklet-service 注册时生成 | `"zNKuN3XwXN7xq2NsJQjjfwujyqCxx26DhwgV"` |
|
|
116
|
+
| `COMPONENT_DID` | var | ✅ | Payment Kit 组件 DID(固定值) | `"z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk"` |
|
|
117
|
+
| `MEDIA_KIT_URL` | var | ✅ | Media Kit Worker 公网 URL | `"https://media-kit.workers.dev"` |
|
|
118
|
+
| `APP_URL` | secret | ⚠️ 自定义域名时必需 | 应用对外 URL;不设则用 `workers.dev` 域名 | `"https://pay.example.com"` |
|
|
119
|
+
| `APP_SK` | secret | ✅ | 应用签名私钥(hex),DID Connect challenge 用 | `"0x..."` |
|
|
120
|
+
| `STRIPE_WEBHOOK_SECRET` | secret | ⚠️ 启用 Stripe 时必需 | Stripe Webhook 签名密钥 | `"whsec_..."` |
|
|
121
|
+
|
|
122
|
+
### 为什么这几个是必需的
|
|
123
|
+
|
|
124
|
+
- `APP_SK` — DID Connect challenge 的 ED25519 签名只能由 app 私钥完成;worker 拿不到 SK 就无法响应 `/api/did-connect/*` 登录流程。
|
|
125
|
+
- `APP_PID` — 所有 `AUTH_SERVICE.resolveIdentity(jwt, authHeader, APP_PID)` 调用都要传 instance DID;没有它 AUTH_SERVICE 无法定位该应用的 EK 和权限矩阵。
|
|
126
|
+
- `MEDIA_KIT_URL` — 前端 `__blocklet__.js` 初始化依赖这个 URL 渲染图片路径;缺失会导致 logo / 商品图 404。
|
|
127
|
+
- `STRIPE_WEBHOOK_SECRET` — CF 部署不再从 DB 读取(Blocklet Server 以前存 DB),**必须**用环境变量覆盖,否则 Stripe webhook 验签直接 400。
|
|
128
|
+
|
|
129
|
+
> **Stripe Secret Key 不需要单独设置**:存储在 `payment_methods.metadata` 里的加密 Stripe key 会在 Worker 首请求时通过 `AUTH_SERVICE.getAppEk(APP_PID)` 拿到 EK 后自动解密。
|
|
130
|
+
|
|
131
|
+
### 不需要设置的变量
|
|
132
|
+
|
|
133
|
+
以下历史变量不再需要,CF runtime 自动处理或从 AUTH_SERVICE 拉取:
|
|
134
|
+
|
|
135
|
+
- ~~`BLOCKLET_APP_EK`~~ — 运行时从 `AUTH_SERVICE.getAppEk(APP_PID)` 获取
|
|
136
|
+
- ~~`BLOCKLET_MODE`~~ — Worker 入口硬编码为 `production`
|
|
137
|
+
- ~~`BLOCKLET_APP_ID` / `BLOCKLET_APP_DID`~~ — 从 `APP_PID` 派生
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## `wrangler.jsonc` 完整模板
|
|
142
|
+
|
|
143
|
+
```jsonc
|
|
144
|
+
{
|
|
145
|
+
"name": "payment-kit",
|
|
146
|
+
"main": "dist/worker.js",
|
|
147
|
+
"compatibility_date": "2024-12-01",
|
|
148
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
149
|
+
"placement": { "mode": "smart" },
|
|
150
|
+
|
|
151
|
+
"assets": {
|
|
152
|
+
"directory": "public",
|
|
153
|
+
"binding": "ASSETS",
|
|
154
|
+
"html_handling": "auto-trailing-slash",
|
|
155
|
+
"not_found_handling": "single-page-application"
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
"d1_databases": [
|
|
159
|
+
{
|
|
160
|
+
"binding": "DB",
|
|
161
|
+
"database_name": "payment-kit-prod",
|
|
162
|
+
"database_id": "<your-d1-database-id>"
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
|
|
166
|
+
"kv_namespaces": [
|
|
167
|
+
{
|
|
168
|
+
"binding": "DID_CONNECT_KV",
|
|
169
|
+
"id": "<your-kv-namespace-id>"
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
|
|
173
|
+
// 可选:启用 Hyperdrive(连接外部 Postgres)
|
|
174
|
+
// "hyperdrive": [
|
|
175
|
+
// { "binding": "HYPERDRIVE", "id": "<your-hyperdrive-id>" }
|
|
176
|
+
// ],
|
|
177
|
+
|
|
178
|
+
"services": [
|
|
179
|
+
{ "binding": "MEDIA_KIT", "service": "media-kit" },
|
|
180
|
+
{
|
|
181
|
+
"binding": "AUTH_SERVICE",
|
|
182
|
+
"service": "blocklet-service",
|
|
183
|
+
"entrypoint": "BlockletServiceRPC"
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
|
|
187
|
+
"vars": {
|
|
188
|
+
"APP_NAME": "Payment Kit",
|
|
189
|
+
"APP_PID": "<your-instance-did>",
|
|
190
|
+
"COMPONENT_DID": "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk",
|
|
191
|
+
"MEDIA_KIT_URL": "https://<your-media-kit>.workers.dev"
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
"queues": {
|
|
195
|
+
"producers": [
|
|
196
|
+
{ "binding": "JOB_QUEUE", "queue": "payment-kit-jobs" }
|
|
197
|
+
],
|
|
198
|
+
"consumers": [
|
|
199
|
+
{
|
|
200
|
+
"queue": "payment-kit-jobs",
|
|
201
|
+
"max_batch_size": 10,
|
|
202
|
+
"max_batch_timeout": 5,
|
|
203
|
+
"max_retries": 3,
|
|
204
|
+
"dead_letter_queue": "payment-kit-jobs-dlq"
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
"triggers": {
|
|
210
|
+
"crons": ["* * * * *"]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 部署流程(按步骤)
|
|
218
|
+
|
|
219
|
+
### Phase 0:前置确认
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# 确认 blocklet-service 已部署且暴露 BlockletServiceRPC
|
|
223
|
+
wrangler deployments list --name blocklet-service
|
|
224
|
+
|
|
225
|
+
# 确认 media-kit 已部署
|
|
226
|
+
wrangler deployments list --name media-kit
|
|
227
|
+
|
|
228
|
+
# 拿到 Payment Kit 的 APP_PID 和 APP_SK(从 blocklet-service admin 注册得到)
|
|
229
|
+
# APP_PID: instance DID
|
|
230
|
+
# APP_SK: 应用私钥 hex
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Phase 1:创建 CF 资源
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
cd blocklets/core/cloudflare
|
|
237
|
+
wrangler d1 create payment-kit-prod
|
|
238
|
+
wrangler kv namespace create DID_CONNECT_KV
|
|
239
|
+
wrangler queues create payment-kit-jobs
|
|
240
|
+
wrangler queues create payment-kit-jobs-dlq
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
把返回的 `database_id` / `kv namespace id` 填到 `wrangler.jsonc` 对应位置。
|
|
244
|
+
|
|
245
|
+
### Phase 2:填写 `wrangler.jsonc`
|
|
246
|
+
|
|
247
|
+
用 Phase 0 拿到的 `APP_PID` + Phase 1 创建的 D1/KV id + media-kit URL 填充上面的模板。
|
|
248
|
+
|
|
249
|
+
### Phase 3:设置 secrets
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
cd blocklets/core/cloudflare
|
|
253
|
+
|
|
254
|
+
# 必需
|
|
255
|
+
wrangler secret put APP_SK
|
|
256
|
+
# 粘贴应用私钥 hex
|
|
257
|
+
|
|
258
|
+
# 启用 Stripe 时必需(只有 webhook secret;Stripe API key 走 DB + EK 解密,不需要 env)
|
|
259
|
+
wrangler secret put STRIPE_WEBHOOK_SECRET
|
|
260
|
+
# 粘贴 whsec_...
|
|
261
|
+
|
|
262
|
+
# 自定义域名时必需
|
|
263
|
+
wrangler secret put APP_URL
|
|
264
|
+
# 粘贴 https://pay.example.com
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Phase 4:构建
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
cd blocklets/core
|
|
271
|
+
|
|
272
|
+
# 1. 前端 SPA
|
|
273
|
+
npx vite build --config cloudflare/vite.config.ts --outDir cloudflare/public
|
|
274
|
+
|
|
275
|
+
# 2. Worker 入口
|
|
276
|
+
cd cloudflare
|
|
277
|
+
node run-build.js
|
|
278
|
+
# 输出: dist/worker.js(~8.7MB)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Phase 5:部署
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
cd blocklets/core/cloudflare
|
|
285
|
+
npx wrangler deploy --config wrangler.jsonc
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 一键脚本
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
cd blocklets/core && \
|
|
292
|
+
npx vite build --config cloudflare/vite.config.ts --outDir cloudflare/public && \
|
|
293
|
+
cd cloudflare && \
|
|
294
|
+
node run-build.js && \
|
|
295
|
+
npx wrangler deploy --config wrangler.jsonc
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Phase 6:首次启动验证
|
|
299
|
+
|
|
300
|
+
部署后访问 `https://<your-worker>.workers.dev/__blocklet__.js`,应当返回:
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
window.blocklet = { appName: 'Payment Kit', appUrl: '...', appPid: '...', ... }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
访问 `https://<your-worker>.workers.dev/api/health`(若存在)或任意 public API,确认 200。
|
|
307
|
+
|
|
308
|
+
**首次请求会触发**:
|
|
309
|
+
|
|
310
|
+
1. `ensureModelsInit()` — Sequelize-D1 sync,在 D1 里自动建表
|
|
311
|
+
2. `initFromAuthService(env)` — 从 `AUTH_SERVICE.getAppEk(APP_PID)` 拉 EK 并初始化 crypto chain(`PBKDF2 → AES password`)
|
|
312
|
+
3. DID Connect 路由挂载 — `APP_SK` + `DID_CONNECT_KV` 都就绪才会挂载
|
|
313
|
+
|
|
314
|
+
查看首请求日志:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
wrangler tail --format pretty
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
应当看到:
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
[Security] Initialized with EK from AUTH_SERVICE
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
如果看到:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
[Security] No APP_EK found for instance <APP_PID>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
→ blocklet-service 里该 APP_PID 没有 EK,回到 Phase 0 重新注册。
|
|
333
|
+
|
|
334
|
+
如果看到:
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
[Security] initFromAuthService failed: ...
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
→ `AUTH_SERVICE` binding 或 `getAppEk` RPC 方法有问题,检查 `wrangler.jsonc` 的 `entrypoint` 是否为 `BlockletServiceRPC`。
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Stripe 集成
|
|
345
|
+
|
|
346
|
+
### Webhook endpoint 切换
|
|
347
|
+
|
|
348
|
+
从 Blocklet Server 迁移到 CF Worker 时,**必须**在 Stripe Dashboard 做以下改动:
|
|
349
|
+
|
|
350
|
+
1. 创建新的 webhook endpoint,URL 改成 `https://<your-worker>.workers.dev/api/stripe/webhook`
|
|
351
|
+
2. 复制新 endpoint 的 signing secret(`whsec_...`)
|
|
352
|
+
3. 用 `wrangler secret put STRIPE_WEBHOOK_SECRET` 设置到 Worker
|
|
353
|
+
4. 确认 webhook events 勾选:
|
|
354
|
+
- `payment_intent.succeeded`
|
|
355
|
+
- `payment_intent.payment_failed`
|
|
356
|
+
- `invoice.payment_succeeded`
|
|
357
|
+
- `invoice.payment_failed`
|
|
358
|
+
- `customer.subscription.*`
|
|
359
|
+
5. **不要**复用旧的 webhook secret —— Stripe 每个 endpoint 有独立 signing key
|
|
360
|
+
|
|
361
|
+
### Stripe key 解密
|
|
362
|
+
|
|
363
|
+
Payment Kit 源代码从 DB 读取加密的 Stripe key,再用 APP_EK 派生的 password 解密。CF 下这条链路保留:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
首请求 → initFromAuthService(env)
|
|
367
|
+
→ AUTH_SERVICE.getAppEk(APP_PID)
|
|
368
|
+
→ PBKDF2(appEk, APP_PID, 256, 32, sha512)
|
|
369
|
+
→ 保存到 _password
|
|
370
|
+
|
|
371
|
+
Stripe 调用 → PaymentMethod.getStripeClient()
|
|
372
|
+
→ decrypt(encryptedKey, _password)
|
|
373
|
+
→ new Stripe(plaintextKey)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
只要 `APP_PID` 复用、`AUTH_SERVICE.getAppEk` 返回的 EK 和原 Blocklet Server 一致,这条链路自动走通,不需要额外设置环境变量。
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## D1 数据库初始化
|
|
381
|
+
|
|
382
|
+
Payment Kit 使用 Sequelize 的 `sync()` 自动建表,首次部署后访问任意 API 即触发。
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
# 查看已建的表
|
|
386
|
+
wrangler d1 execute payment-kit-prod --remote \
|
|
387
|
+
--command "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"
|
|
388
|
+
|
|
389
|
+
# 查看特定表结构
|
|
390
|
+
wrangler d1 execute payment-kit-prod --remote \
|
|
391
|
+
--command "PRAGMA table_info(customers);"
|
|
392
|
+
|
|
393
|
+
# 检查某个关键迁移(示例:locks 表)
|
|
394
|
+
wrangler d1 execute payment-kit-prod --remote \
|
|
395
|
+
--command "SELECT COUNT(*) FROM locks;"
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
如果需要从 Blocklet Server 迁移现有数据,参考 `MIGRATION-RUNBOOK.md` 的 Phase 3-4。
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Mount Point 模式
|
|
403
|
+
|
|
404
|
+
当 Payment Kit 被其他 Worker(如 AIGNE Hub)通过 Service Binding 以 mount point 挂载(如 `/payment/*`)时,Payment Kit 自动:
|
|
405
|
+
|
|
406
|
+
1. 从 `X-Mount-Prefix` header 读取挂载前缀
|
|
407
|
+
2. `__blocklet__.js` 返回正确的 `prefix` 和 `groupPrefix`
|
|
408
|
+
3. 前端 `window.blocklet.prefix` 由 gateway 重写
|
|
409
|
+
|
|
410
|
+
集成方式详见 `test-aigne-hub/README.md`。
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## 认证流程
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
浏览器 → Payment Kit Worker
|
|
418
|
+
├── Hono caller middleware
|
|
419
|
+
│ ├── 从 Cookie 取 login_token JWT
|
|
420
|
+
│ ├── AUTH_SERVICE.resolveIdentity(jwt, authHeader, APP_PID)
|
|
421
|
+
│ └── 缓存 { did, role, displayName, avatar } 到 Hono context
|
|
422
|
+
├── Express adapter
|
|
423
|
+
│ ├── 注入 req.user + x-user-* headers
|
|
424
|
+
│ └── security.ts 按 role 检查权限
|
|
425
|
+
└── 路由处理
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
- `component.call` 路径(`x-component-sig`):CF 下委托给 AUTH_SERVICE,不做本地 ED25519 验签
|
|
429
|
+
- Service Binding 调用:通过 Bearer access-key 或 Cookie JWT 认证,走同一条 AUTH_SERVICE 验证链路
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## 常见问题
|
|
434
|
+
|
|
435
|
+
### Worker 启动返回 503 "AUTH_SERVICE not configured"
|
|
436
|
+
|
|
437
|
+
- `wrangler.jsonc` 里 `services[].binding` 必须是 `AUTH_SERVICE`(全大写、准确拼写)
|
|
438
|
+
- 目标 service name 必须与 blocklet-service 的 Worker name 一致
|
|
439
|
+
- `entrypoint` 必须是 `BlockletServiceRPC`
|
|
440
|
+
|
|
441
|
+
### 首请求日志打 "No APP_EK found for instance"
|
|
442
|
+
|
|
443
|
+
- Blocklet Server admin 侧需要确认该 APP_PID 已经注册并生成了 EK
|
|
444
|
+
- 多次重启或重新注册过应用时,旧的 APP_PID 可能失效 → 拿新的 APP_PID 更新 `wrangler.jsonc`
|
|
445
|
+
|
|
446
|
+
### Stripe webhook 永远 400 "signature verification failed"
|
|
447
|
+
|
|
448
|
+
- `STRIPE_WEBHOOK_SECRET` 一定是 CF Worker endpoint 对应的那个 `whsec_...`,不是 Blocklet Server 旧的
|
|
449
|
+
- 如果 Stripe Dashboard 里还指向旧 URL,新 endpoint 永远收不到事件
|
|
450
|
+
|
|
451
|
+
### 前端图片/logo 404
|
|
452
|
+
|
|
453
|
+
- 检查 `MEDIA_KIT_URL` 是否指向可访问的 Media Kit Worker
|
|
454
|
+
- 确认 media-kit Worker 已经部署并且 public endpoint 可用
|
|
455
|
+
|
|
456
|
+
### DID Connect 登录卡在 challenge 阶段
|
|
457
|
+
|
|
458
|
+
- `APP_SK` 必须是 hex 字符串,对应的 public key 必须在 blocklet-service 里注册为该 APP_PID 的 key
|
|
459
|
+
- `DID_CONNECT_KV` 必须已创建并 binding 正确
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## 目录结构
|
|
464
|
+
|
|
465
|
+
```
|
|
466
|
+
blocklets/core/cloudflare/
|
|
467
|
+
├── worker.ts # Worker 入口(Hono 路由 + Express 适配)
|
|
468
|
+
├── run-build.js # esbuild 构建脚本
|
|
469
|
+
├── build.ts # 构建配置 + shim 别名映射
|
|
470
|
+
├── vite.config.ts # 前端 Vite 构建配置
|
|
471
|
+
├── did-connect-auth.ts # DID Connect 登录路由(挂载需要 APP_SK + DID_CONNECT_KV)
|
|
472
|
+
├── wrangler.jsonc # 生产配置模板
|
|
473
|
+
├── wrangler.migration-test.json # 迁移测试环境配置
|
|
474
|
+
├── index.html # 前端开发入口
|
|
475
|
+
├── public/ # 前端构建产物(Vite 输出)
|
|
476
|
+
├── dist/ # Worker 构建产物(esbuild 输出)
|
|
477
|
+
├── shims/ # Node.js → CF Workers 适配层
|
|
478
|
+
│ ├── blocklet-sdk/
|
|
479
|
+
│ │ ├── security.ts # EK 初始化 + encrypt/decrypt(走 AUTH_SERVICE.getAppEk)
|
|
480
|
+
│ │ ├── verify-sign.ts # 组件签名验证(委托 AUTH_SERVICE)
|
|
481
|
+
│ │ └── ...
|
|
482
|
+
│ ├── sequelize-d1/ # Sequelize → D1 适配
|
|
483
|
+
│ ├── queue.ts # fastq → CF Queue 适配
|
|
484
|
+
│ ├── lock.ts # 分布式锁(D1 实现)
|
|
485
|
+
│ ├── cron.ts # 定时任务
|
|
486
|
+
│ └── ...
|
|
487
|
+
└── test-aigne-hub/ # Gateway 集成参考实现
|
|
488
|
+
├── worker.ts
|
|
489
|
+
├── wrangler.json
|
|
490
|
+
└── README.md # 集成指南
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 相关文档
|
|
496
|
+
|
|
497
|
+
- `MIGRATION-RUNBOOK.md` — 从 Blocklet Server 迁移到 CF Workers 的完整流程(数据迁移、切流、回滚)
|
|
498
|
+
- `MIGRATION-CHALLENGES.md` — 迁移过程遇到的典型问题和解决方案
|
|
499
|
+
- `test-aigne-hub/README.md` — Service Binding mount point 集成参考
|