@h-ai/cli 0.1.0-alpha.67 → 0.1.0-alpha.68
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/README.en.md +40 -4
- package/README.ja.md +40 -4
- package/README.md +40 -4
- package/dist/{src-2V6VP4I5.js → chunk-FI56GONY.js} +32 -3103
- package/dist/chunk-FI56GONY.js.map +1 -0
- package/dist/chunk-MBPB37FR.js +1348 -0
- package/dist/chunk-MBPB37FR.js.map +1 -0
- package/dist/{chunk-7DSURTJ4.js → chunk-ZFNBUF77.js} +327 -199
- package/dist/chunk-ZFNBUF77.js.map +1 -0
- package/dist/cli-config-templates-W2LLPMB6.js +5 -0
- package/dist/{cli-config-templates-6AHNUGBN.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
- package/dist/{cli-skill-templates-FC3QRKDO.js → cli-skill-templates-US5CCAJ4.js} +3 -3
- package/dist/{cli-skill-templates-FC3QRKDO.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
- package/dist/index.js +920 -336
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -0
- package/dist/src-DSJJXM2F.js +3849 -0
- package/dist/src-DSJJXM2F.js.map +1 -0
- package/package.json +9 -8
- package/templates/apps/admin/messages/ja-JP.json +38 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
- package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +5 -1
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
- package/templates/apps/api/package.json.hbs +1 -1
- package/templates/apps/api/playwright.config.ts.hbs +15 -7
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +1 -1
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +19 -8
- package/templates/apps/fullstack/package.json.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +5 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +9 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +5 -5
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +11 -12
- package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
- package/templates/apps/h5/messages/ja-JP.json +40 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +14 -3
- package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +13 -3
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
- package/templates/apps/website/messages/ja-JP.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +10 -2
- package/templates/base/e2e/app.spec.ts.hbs +13 -8
- package/templates/base/package.json.hbs +4 -1
- package/templates/base/playwright.config.ts +15 -4
- package/templates/base/src/hooks.server.ts.hbs +15 -6
- package/templates/base/src/lib/server/init.ts.hbs +139 -30
- package/templates/base/vite.config.ts.hbs +2 -2
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
- package/templates/i18n/project.inlang/settings.json +1 -1
- package/templates/shared/e2e-env.ts +98 -0
- package/templates/shared/run-e2e.mjs +51 -12
- package/templates/skills/AGENTS.md +4 -0
- package/templates/skills/bridges/admin/AGENTS.md +5 -1
- package/templates/skills/bridges/api/AGENTS.md +2 -0
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +4 -0
- package/templates/skills/bridges/h5/AGENTS.md +5 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +5 -1
- package/templates/skills/bridges/website/AGENTS.md +5 -1
- package/templates/skills/hai-ai/SKILL.md +8 -2
- package/templates/skills/hai-ai/guide.md +134 -60
- package/templates/skills/hai-ai/reference.md +51 -11
- package/templates/skills/hai-api-client/SKILL.md +2 -0
- package/templates/skills/hai-api-client/reference.md +17 -5
- package/templates/skills/hai-api-contract/SKILL.md +1 -0
- package/templates/skills/hai-api-contract/reference.md +4 -2
- package/templates/skills/hai-app-create/SKILL.md +4 -0
- package/templates/skills/hai-app-create/development.md +20 -4
- package/templates/skills/hai-app-create/reference.md +2 -1
- package/templates/skills/hai-app-review/SKILL.md +6 -2
- package/templates/skills/hai-app-review/differential.md +34 -0
- package/templates/skills/hai-app-tests/SKILL.md +3 -1
- package/templates/skills/hai-app-tests/reference.md +4 -4
- package/templates/skills/hai-app-tests/validation.md +10 -3
- package/templates/skills/hai-audit/SKILL.md +10 -8
- package/templates/skills/hai-audit/reference.md +3 -0
- package/templates/skills/hai-build/reference.md +6 -4
- package/templates/skills/hai-cache/SKILL.md +9 -3
- package/templates/skills/hai-cache/reference.md +53 -11
- package/templates/skills/hai-capacitor/SKILL.md +3 -1
- package/templates/skills/hai-capacitor/reference.md +9 -5
- package/templates/skills/hai-core/SKILL.md +4 -0
- package/templates/skills/hai-core/reference.md +25 -5
- package/templates/skills/hai-crypto/SKILL.md +10 -2
- package/templates/skills/hai-crypto/reference.md +64 -10
- package/templates/skills/hai-datapipe/SKILL.md +2 -0
- package/templates/skills/hai-datapipe/reference.md +3 -1
- package/templates/skills/hai-deploy/SKILL.md +9 -4
- package/templates/skills/hai-deploy/middleware.md +51 -37
- package/templates/skills/hai-iam/SKILL.md +24 -8
- package/templates/skills/hai-iam/reference.md +181 -154
- package/templates/skills/hai-kit/SKILL.md +23 -7
- package/templates/skills/hai-kit/reference.md +42 -24
- package/templates/skills/hai-payment/SKILL.md +13 -11
- package/templates/skills/hai-payment/reference.md +80 -41
- package/templates/skills/hai-pr-review/SKILL.md +21 -88
- package/templates/skills/hai-reach/SKILL.md +5 -2
- package/templates/skills/hai-reach/reference.md +83 -22
- package/templates/skills/hai-reldb/SKILL.md +5 -2
- package/templates/skills/hai-reldb/reference.md +68 -25
- package/templates/skills/hai-scheduler/SKILL.md +8 -5
- package/templates/skills/hai-scheduler/reference.md +38 -24
- package/templates/skills/hai-serv/SKILL.md +23 -8
- package/templates/skills/hai-serv/reference.md +123 -87
- package/templates/skills/hai-spec-implement/SKILL.md +4 -0
- package/templates/skills/hai-spec-implement/evaluation.md +18 -0
- package/templates/skills/hai-spec-implement/handoff.md +47 -0
- package/templates/skills/hai-spec-implement/templates.md +5 -0
- package/templates/skills/hai-spec-implement/workflow.md +2 -0
- package/templates/skills/hai-spec-review/SKILL.md +4 -0
- package/templates/skills/hai-storage/SKILL.md +8 -2
- package/templates/skills/hai-storage/reference.md +64 -25
- package/templates/skills/hai-ui/SKILL.md +16 -2
- package/templates/skills/hai-ui/guide.md +3 -3
- package/templates/skills/hai-ui/reference.md +17 -6
- package/templates/skills/hai-ui-design/SKILL.md +31 -0
- package/templates/skills/hai-ui-design/patterns.md +26 -0
- package/templates/skills/hai-ui-review/SKILL.md +30 -0
- package/templates/skills/hai-ui-review/checklist.md +22 -0
- package/templates/skills/hai-vecdb/SKILL.md +12 -8
- package/templates/skills/hai-vecdb/reference.md +33 -3
- package/dist/chunk-7DSURTJ4.js.map +0 -1
- package/dist/chunk-AOQ4N657.js +0 -640
- package/dist/chunk-AOQ4N657.js.map +0 -1
- package/dist/cli-config-templates-6AHNUGBN.js +0 -5
- package/dist/src-2V6VP4I5.js.map +0 -1
|
@@ -5,15 +5,19 @@ description: "在 SvelteKit 中接入 hooks、认证守卫、输入校验、响
|
|
|
5
5
|
|
|
6
6
|
# hai-kit
|
|
7
7
|
|
|
8
|
+
限流采用首次成功提交为起点的固定窗口,后续请求不延长 TTL。必须显式传 store;应用先检查共享 Redis 初始化结果,所有节点使用相同 namespace/windowMs/maxRequests。缓存读取/条件提交失败或冲突耗尽返回503,超限429;不会回退 get+set 或本地计数。createMemoryRateLimitStore 仅开发或显式 HAI_E2E/HAI_E2E_MOCK 测试使用,无常驻定时器,丢弃引用即可回收;每次请求最多惰性扫描 64 项,最多保存 10000 个窗口,容量满时对新 key 返回503且不驱逐有效计数,外部 store 生命周期由创建者管理;生产不得设置测试开关。响应重置时间为节点时钟估算,实际窗口由 Redis TTL 决定,须同步时钟。默认键只读取可信 adapter 的 getClientAddress、规范化等价IPv6和IPv4映射地址,不采信原始转发头;代理信任通过部署adapter配置。
|
|
9
|
+
|
|
10
|
+
transport 仅支持 v2:SM2 发送方签名、独立密钥的 Encrypt-then-HMAC、HTTP 上下文/响应状态绑定及共享原子防重放。GET/HEAD、空 POST 与空响应也认证;无降级开关。TLS 信任边界、5 分钟窗口、nonce 持久化与实现示例见 [参考](reference.md)。
|
|
11
|
+
|
|
8
12
|
## 能力契约
|
|
9
13
|
|
|
10
|
-
| 项目
|
|
11
|
-
|
|
|
12
|
-
| 能力
|
|
13
|
-
| 适用场景 | SvelteKit hooks、load 和同源端点集成
|
|
14
|
-
| 输入
|
|
15
|
-
| 输出
|
|
16
|
-
| 限制
|
|
14
|
+
| 项目 | 契约 |
|
|
15
|
+
| -------- | ------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| 能力 | 使用 @h-ai/kit 集成 SvelteKit hooks、守卫、校验、响应及同源客户端传输 |
|
|
17
|
+
| 适用场景 | SvelteKit hooks、load 和同源端点集成 |
|
|
18
|
+
| 输入 | SvelteKit handle/RequestEvent、schema、guard、transport |
|
|
19
|
+
| 输出 | handle、Response、校验值和客户端请求结果 |
|
|
20
|
+
| 限制 | 无 init/close;保留 SvelteKit 控制流异常。跨端共享 HTTP contract 用 api-contract + serv,勿复制到 kit。 |
|
|
17
21
|
|
|
18
22
|
## 使用路径
|
|
19
23
|
|
|
@@ -22,6 +26,12 @@ description: "在 SvelteKit 中接入 hooks、认证守卫、输入校验、响
|
|
|
22
26
|
3. 区分返回 HaiResult 的业务操作与抛 Response/redirect/error 的框架控制流,不吞掉控制流。
|
|
23
27
|
4. 对齐 Cookie/CSRF/transport 与服务端配置,验证匿名、登录、权限拒绝和表单失败。
|
|
24
28
|
|
|
29
|
+
服务端 transport 使用含受管 serverKeyRing、共享 keyStore 的对象,不再接受单独 true。公开 _kit.yml 只保留路径/开关,不放私钥;浏览器无 ring。请求携带协商 serverKeyId,各节点同 ring + registry,不依赖粘性路由;轮换见 hai-crypto。
|
|
30
|
+
|
|
31
|
+
传输加密按实际请求/响应 body 判断空正文,不信任 Content-Type/Length;非空明文不能借缺头、chunked 或伪造零长度旁路,实际空流保留无 input POST 行为。
|
|
32
|
+
|
|
33
|
+
transport 与 serv/客户端共享固定 UTF-8 字节预算:握手 JSON 16 KiB、公钥 4 KiB、明文 1 MiB、密文 JSON 2 MiB + 32 KiB。直接有界读取原流,超限/取消停止读取、取消流并释放 reader;请求超限 413、响应超限 500。重建 Request 保留 signal,重建正文移除旧长度/编码头;预算不属于 `_kit.yml` 或环境变量。
|
|
34
|
+
|
|
25
35
|
## 按需参考
|
|
26
36
|
|
|
27
37
|
按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
|
|
@@ -30,3 +40,9 @@ description: "在 SvelteKit 中接入 hooks、认证守卫、输入校验、响
|
|
|
30
40
|
- [核心 API](reference.md#核心-api)
|
|
31
41
|
- [常见模式](reference.md#常见模式)
|
|
32
42
|
- [声明式 CRUD 表单字段](reference.md#声明式-crud-表单字段)
|
|
43
|
+
|
|
44
|
+
Node 请求/SSR 必须用 `core.i18n.runWithLocale(locale, callback)`(kit 同名方法)包裹实际异步 resolve;serv 自动建立请求语言上下文。浏览器 setGlobalLocale/createLocaleStore 继续管理应用语言,SSR 的 UI setter/store 不写全局值。显式 MessageOptions.locale 优先;进程默认语言仅在启动时设置。
|
|
45
|
+
|
|
46
|
+
认证 Cookie 在 NODE_ENV 或 HAI_CORE_ENV 为 production 时默认启用 Secure,HAI_E2E/HAI_E2E_MOCK 不降低 Cookie 安全;限流生产判断也识别这两个环境标志,测试开关仅放宽共享状态要求。
|
|
47
|
+
|
|
48
|
+
`kit.auth.logout(cookies, accessToken)` 返回 `Promise<HaiResult<void>>`。无令牌时仅清除 Cookie 并成功;有令牌但未配置认证操作时返回失败。无论服务端结果如何都清除本地 Cookie,调用方仍必须把失败转换为错误响应或提示;仅成功结果代表服务端撤销已经确认。底层控制流异常继续传播。
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
> `@h-ai/kit` 只负责 SvelteKit 集成:`handle` hook、guard、response、validate、client、auth、crud、A2A。公共跨端 HTTP API 契约统一使用 `@h-ai/api-contract` + `@h-ai/serv`,不要在 kit 中定义业务 API contract。
|
|
4
4
|
|
|
5
|
+
认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
|
|
6
|
+
|
|
7
|
+
空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
|
|
8
|
+
|
|
5
9
|
## 使用步骤
|
|
6
10
|
|
|
7
11
|
### 1. 请求管道
|
|
8
12
|
|
|
9
13
|
```ts
|
|
10
14
|
import type { Handle } from '@sveltejs/kit'
|
|
15
|
+
import { cache } from '@h-ai/cache'
|
|
11
16
|
import { kit } from '@h-ai/kit'
|
|
12
17
|
|
|
13
18
|
const haiHandle = kit.createHandle({
|
|
@@ -16,13 +21,15 @@ const haiHandle = kit.createHandle({
|
|
|
16
21
|
loginUrl: '/auth/login',
|
|
17
22
|
protectedPaths: ['/admin/*'],
|
|
18
23
|
},
|
|
19
|
-
rateLimit: { windowMs: 60_000, maxRequests: 100 },
|
|
24
|
+
rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
|
|
20
25
|
logging: true,
|
|
21
26
|
})
|
|
22
27
|
|
|
23
28
|
export const handle: Handle = haiHandle
|
|
24
29
|
```
|
|
25
30
|
|
|
31
|
+
限流采用首次成功提交为起点的固定窗口,后续请求不延长 TTL。必须显式传 store;应用先检查共享 Redis 初始化结果,所有节点使用相同 namespace/windowMs/maxRequests。缓存读取/条件提交失败或冲突耗尽返回503,超限429;不会回退 get+set 或本地计数。createMemoryRateLimitStore 仅开发或显式 HAI_E2E/HAI_E2E_MOCK 测试使用,无常驻定时器,丢弃引用即可回收;每次请求最多惰性扫描 64 项,最多保存 10000 个窗口,容量满时对新 key 返回503且不驱逐有效计数,外部 store 生命周期由创建者管理;生产不得设置测试开关。响应重置时间为节点时钟估算,实际窗口由 Redis TTL 决定,须同步时钟。默认键只读取可信 adapter 的 getClientAddress、规范化等价IPv6和IPv4映射地址,不采信原始转发头;代理信任通过部署adapter配置。
|
|
32
|
+
|
|
26
33
|
### 2. SvelteKit API endpoint
|
|
27
34
|
|
|
28
35
|
```ts
|
|
@@ -47,7 +54,7 @@ const client = kit.client.create()
|
|
|
47
54
|
export const { apiFetch } = client
|
|
48
55
|
```
|
|
49
56
|
|
|
50
|
-
> 同源 endpoint
|
|
57
|
+
> 同源 endpoint 使用 httpOnly Cookie;`auth: true` 只读取 `kit.auth.setBrowserToken()` 写入的页面内存 Token。自定义 `BrowserTokenStore` 也须使用内存或平台安全存储,禁止把敏感凭据持久化到 Web Storage。
|
|
51
58
|
|
|
52
59
|
### 4. 同源传输加密
|
|
53
60
|
|
|
@@ -56,10 +63,14 @@ kit 不再维护本地传输加密实现;服务端和客户端都统一委托
|
|
|
56
63
|
```ts
|
|
57
64
|
// hooks.server.ts
|
|
58
65
|
import { cache } from '@h-ai/cache'
|
|
59
|
-
import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
|
|
66
|
+
import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
60
67
|
import { kit } from '@h-ai/kit'
|
|
61
68
|
|
|
62
69
|
await crypto.init()
|
|
70
|
+
const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
71
|
+
if (!ring.success)
|
|
72
|
+
throw new Error(ring.error.message)
|
|
73
|
+
const serverKeyRing = ring.data
|
|
63
74
|
await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
|
|
64
75
|
|
|
65
76
|
export const handle = kit.createHandle({
|
|
@@ -71,7 +82,7 @@ export const handle = kit.createHandle({
|
|
|
71
82
|
crypto: {
|
|
72
83
|
crypto,
|
|
73
84
|
transport: {
|
|
74
|
-
|
|
85
|
+
serverKeyRing,
|
|
75
86
|
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
76
87
|
},
|
|
77
88
|
},
|
|
@@ -80,13 +91,13 @@ export const handle = kit.createHandle({
|
|
|
80
91
|
|
|
81
92
|
需要保护会话 Cookie 时配置 `crypto.encryptedCookies`,并通过 `crypto.cookieEncryptionKey` 或 `HAI_KIT_COOKIE_KEY` 提供密钥。缺少密钥会在 `createHandle()` 时失败;加解密错误不会回退到明文 Cookie。
|
|
82
93
|
|
|
83
|
-
```
|
|
84
|
-
|
|
94
|
+
```svelte
|
|
95
|
+
<!-- src/routes/+layout.svelte -->
|
|
85
96
|
<script lang='ts'>
|
|
86
97
|
import { browser } from '$app/environment'
|
|
98
|
+
import { appKitConfig } from '$lib/config/kit-config'
|
|
87
99
|
import { crypto } from '@h-ai/crypto'
|
|
88
100
|
import { kit } from '@h-ai/kit'
|
|
89
|
-
import { appKitConfig } from '$lib/config/kit-config'
|
|
90
101
|
|
|
91
102
|
// 一次性安装:内部按 appKitConfig.transport 是否启用决定行为,并预热 crypto
|
|
92
103
|
if (browser) {
|
|
@@ -104,11 +115,15 @@ export const { apiFetch } = kit.client.create()
|
|
|
104
115
|
|
|
105
116
|
默认协商端点:`/api/_hai/key-exchange`。如 `keyExchangePath` 自定义,浏览器端同步设置 `keyExchangeUrl`。
|
|
106
117
|
|
|
107
|
-
|
|
118
|
+
启用 transport 使用 `{ serverKeyRing, keyStore, ...公开路径配置 }`,两项必填,不再接受单独的 true。serverKeyRing 通过服务端 Secret/私有 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING` 安全解析,不放 `_kit.yml`、PUBLIC_/VITE_ 或日志;浏览器仅需公开 transport 配置和 crypto。各节点使用同一版本化 ring 与共享 Redis/数据库 registry;仅共享客户端公钥不够。请求带协商 serverKeyId,无需 sticky session;轮换先分发新旧 ring,再切 activeKeyId 重建 manager,删除旧 ID 后客户端 destroy 并重协商。开发/测试须显式选择内存 registry;原公开 maxClients 已删除。完整轮换步骤见 hai-crypto。
|
|
108
119
|
|
|
109
120
|
transport 默认保护同源 `/api/*` endpoint 与 SvelteKit `__data.json` 页面数据请求;页面文档、静态资源与 `multipart/form-data` 上传请求保持明文透传。
|
|
110
121
|
|
|
111
|
-
|
|
122
|
+
受保护路径不允许协议降级;缺少认证、管理器不可用、响应加密失败均拒绝。明文接口仅通过 excludePaths 显式排除。
|
|
123
|
+
|
|
124
|
+
空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
|
|
125
|
+
|
|
126
|
+
与 serv/客户端共享 `@h-ai/crypto` 根入口的 `TRANSPORT_BODY_LIMITS` / `readTransportBody`:握手 JSON 16 KiB、公钥 4 KiB、明文 1 MiB、密文 JSON 2 MiB + 32 KiB,按 UTF-8 字节计数。原始流逐块检查,超限/取消停止并取消流、释放 reader,不 clone/tee 后完整缓冲。请求超限返回 413,响应超限返回 500,无效公钥返回 400;重建 Request 保留 signal,重建正文清除旧 Content-Length/Content-Encoding。固定协议预算不写入 `_kit.yml` 或环境变量。
|
|
112
127
|
|
|
113
128
|
### 5. 使用 `_kit.yml` 统一 transport 配置
|
|
114
129
|
|
|
@@ -122,13 +137,10 @@ server:
|
|
|
122
137
|
|
|
123
138
|
transport:
|
|
124
139
|
keyExchangePath: /api/_hai/key-exchange
|
|
125
|
-
requireEncryption: true
|
|
126
|
-
encryptResponse: true
|
|
127
140
|
excludePaths:
|
|
128
141
|
- /api/storage
|
|
129
142
|
- /api/public
|
|
130
143
|
- /api/auth/profile/avatar
|
|
131
|
-
maxClients: 10000
|
|
132
144
|
```
|
|
133
145
|
|
|
134
146
|
```ts
|
|
@@ -144,19 +156,19 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
|
144
156
|
|
|
145
157
|
## 核心 API
|
|
146
158
|
|
|
147
|
-
| API
|
|
148
|
-
|
|
|
149
|
-
| `kit.createHandle()`
|
|
150
|
-
| `kit.sequence()`
|
|
151
|
-
| `kit.handler()`
|
|
152
|
-
| `kit.guard.require()`
|
|
153
|
-
| `kit.response.*`
|
|
154
|
-
| `kit.validate.*`
|
|
155
|
-
| `kit.auth.*`
|
|
156
|
-
| `kit.client.create()`
|
|
159
|
+
| API | 用途 |
|
|
160
|
+
| -------------------------------------------------------- | -------------------------------------------------------- |
|
|
161
|
+
| `kit.createHandle()` | 创建 SvelteKit Handle |
|
|
162
|
+
| `kit.sequence()` | 组合多个 Handle |
|
|
163
|
+
| `kit.handler()` | API endpoint 错误边界 |
|
|
164
|
+
| `kit.guard.require()` | 权限守卫 |
|
|
165
|
+
| `kit.response.*` | 统一响应工厂 |
|
|
166
|
+
| `kit.validate.*` | Zod 请求校验 |
|
|
167
|
+
| `kit.auth.*` | Cookie / Token 辅助 |
|
|
168
|
+
| `kit.client.create()` | 浏览器端同源 apiFetch |
|
|
157
169
|
| `kit.client.installBrowserTransport(config, { crypto })` | 推荐入口:按解析后的 `_kit.yml` 一键安装浏览器端传输加密 |
|
|
158
|
-
| `kit.client.installBrowserTransportFetch()`
|
|
159
|
-
| `kit.crud.define()`
|
|
170
|
+
| `kit.client.installBrowserTransportFetch()` | 底层入口:直接传 transport 配置安装浏览器全局 fetch 包装 |
|
|
171
|
+
| `kit.crud.define()` | 声明式 CRUD 资源 |
|
|
160
172
|
|
|
161
173
|
## 常见模式
|
|
162
174
|
|
|
@@ -170,3 +182,9 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
|
170
182
|
## 声明式 CRUD 表单字段
|
|
171
183
|
|
|
172
184
|
`kit.crud.define` 的字段支持 `colSpan: 'auto' | 1 | 2`,以及 `validation.message: string | (() => string)`。布局通过 UI 的 `CrudPage.form` 配置;客户端校验用于交互提示,服务端仍使用 Zod 验证和现有权限守卫。UI 公共契约见相邻 [hai-ui skill](../hai-ui/SKILL.md)。
|
|
185
|
+
|
|
186
|
+
Node 请求/SSR 必须用 `core.i18n.runWithLocale(locale, callback)`(kit 同名方法)包裹实际异步 resolve;serv 自动建立请求语言上下文。浏览器 setGlobalLocale/createLocaleStore 继续管理应用语言,SSR 的 UI setter/store 不写全局值。显式 MessageOptions.locale 优先;进程默认语言仅在启动时设置。
|
|
187
|
+
|
|
188
|
+
认证 Cookie 在 NODE_ENV 或 HAI_CORE_ENV 为 production 时默认启用 Secure,HAI_E2E/HAI_E2E_MOCK 不降低 Cookie 安全;限流生产判断也识别这两个环境标志,测试开关仅放宽共享状态要求。
|
|
189
|
+
|
|
190
|
+
`kit.auth.logout(cookies, accessToken)` 返回 `Promise<HaiResult<void>>`。无令牌时仅清除 Cookie 并成功;有令牌但未配置认证操作时返回失败。无论服务端结果如何都清除本地 Cookie,调用方仍必须把失败转换为错误响应或提示;仅成功结果代表服务端撤销已经确认。底层控制流异常继续传播。
|
|
@@ -7,20 +7,22 @@ description: "接入微信、支付宝或 Stripe 的订单、回调验签、查
|
|
|
7
7
|
|
|
8
8
|
## 能力契约
|
|
9
9
|
|
|
10
|
-
| 项目
|
|
11
|
-
|
|
|
12
|
-
| 能力
|
|
13
|
-
| 适用场景 | 服务端支付集成及客户端调起流程
|
|
14
|
-
| 输入
|
|
15
|
-
| 输出
|
|
16
|
-
| 限制
|
|
10
|
+
| 项目 | 契约 |
|
|
11
|
+
| -------- | ------------------------------------------------------------------------------------------- |
|
|
12
|
+
| 能力 | 使用 @h-ai/payment 创建支付订单、验证回调、查单和退款,客户端调起支付 |
|
|
13
|
+
| 适用场景 | 服务端支付集成及客户端调起流程 |
|
|
14
|
+
| 输入 | Provider 配置、订单/退款参数、原始回调内容 |
|
|
15
|
+
| 输出 | 服务端 HaiResult;客户端 invokePayment 按 client 契约使用 |
|
|
16
|
+
| 限制 | 服务端保存凭据并验签;客户端完成不代表付款成功,以可信回调/查单为准。订单履约与重试需幂等。 |
|
|
17
17
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
|
-
1. 服务端校验 Provider 配置后 init
|
|
21
|
-
2.
|
|
22
|
-
3. 回调使用原始 body 与 headers
|
|
23
|
-
4.
|
|
20
|
+
1. 服务端校验 Provider 配置后 init;微信必填 platformKeys(平台 serial/公钥ID→PEM,轮换保留新旧项),支付宝必填 sellerId 和可信 HTTPS returnUrl。Stripe 先初始化共享 reldb,记录渠道映射/幂等结果,关闭时顺序相反。真实凭据经私有配置注入;浏览器只导入 `/client`。
|
|
21
|
+
2. 订单号、金额单位、商品与用户由服务端可信数据决定,先保存业务订单再创建支付。微信仅 jsapi,必填当前公众号 openid,微信/支付宝仅 CNY;支付宝仅 h5,用离线签名的 `{ gateway, fields }` 向固定网关 POST,成功仅表示生成页面参数。Stripe 仅 h5 托管 Checkout,配置 successUrl(必填)/cancelUrl(可选)。公共 TradeType 仅 jsapi/h5;App、Native、小程序及其它组合在联网前拒绝,客户端也拒绝。客户端先检查 success 再检查 invoked,不解析不可信 HTML,不接受任意 action/字段。
|
|
22
|
+
3. 回调使用原始 body 与 headers 验签;核对商户/应用身份与严格时间格式,微信签名时钟仅容差 300 秒,支付宝允许合法跨日重试。所有 Provider 返回必填 eventId/currency,应用事务核对渠道、订单、金额、币种,以共享数据库原子提交订单状态/通知去重/outbox,提交后才应答成功;外部副作用需稳定键幂等。Stripe completed/unpaid 为 pending,只有 status=paid 才履约且防止乱序回退。客户端调起成功不代表付款完成。
|
|
23
|
+
4. 查单、退款与回调重试覆盖成功、失败和重复路径;支付宝查单/退款/关单的 HTTP 200 不代表业务成功,缺失/错误 envelope、非 10000 业务码或关键字段缺失返回对应操作失败;退款 success 后还须检查 status,fund_change=N/缺失为 processing,向渠道确认退款结果。Stripe orderNo/refundNo 固定请求参数,不能覆盖 metadata.orderNo,未知结果超过 23 小时先核对渠道,不删除记录后盲重试。关单须确认 Session expired,已付款不能关闭;页面跳转不能作履约凭据。真实支付仅在用户授权范围内,最后 close。
|
|
24
|
+
|
|
25
|
+
配置示例区分 `PaymentConfigInput` 与 Schema 解析后的 `PaymentConfig`;公开调用只使用包根入口或浏览器 `/client`。先判断 `HaiResult.success`,再判断付款/退款状态或客户端 `invoked`。源码内部签名/协议/幂等仓库例子仅用于解释实现,不应深层导入;类型示例不证明真实商户、网关或手机设备支付。
|
|
24
26
|
|
|
25
27
|
## 按需参考
|
|
26
28
|
|
|
@@ -2,24 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
> `@h-ai/payment` 是 hai-framework 的统一支付模块,通过 Provider 模式支持微信支付、支付宝、Stripe。服务端处理订单和回调,客户端调起支付。
|
|
4
4
|
|
|
5
|
+
公开类型示例按实际包入口校验:初始化输入为 `PaymentConfigInput`,配置 Schema 输出为 `PaymentConfig`;`HaiPaymentError` 的单个成员是错误定义,比较 `result.error.code` 时使用其 `.code`。私有协议、签名、操作仓库不属于应用导入入口。JSDoc 的类型校验不会执行渠道支付或业务履约。
|
|
6
|
+
|
|
5
7
|
## 运行环境
|
|
6
8
|
|
|
7
9
|
> **服务端 + 浏览器分工模块。** `payment.createOrder` / `payment.handleNotify` / `payment.refund` 等操作在 Node.js 端执行。浏览器端使用 `invokePayment()`(从 `@h-ai/payment/client` 导入)调起支付。
|
|
8
10
|
|
|
9
11
|
## 依赖
|
|
10
12
|
|
|
11
|
-
| 模块
|
|
12
|
-
|
|
|
13
|
-
| `@h-ai/audit` | 审计日志(支付操作自动审计)
|
|
13
|
+
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
14
|
+
| ------------- | ----------------------------------- | ------------------ | ---------------------------------------- |
|
|
15
|
+
| `@h-ai/audit` | 审计日志(支付操作自动审计) | 可选 | 已初始化时自动写入审计日志 |
|
|
16
|
+
| `@h-ai/reldb` | Stripe 渠道映射、操作预留与幂等结果 | 启用 Stripe 时必需 | 先初始化共享 DB;payment 关闭后再关闭 DB |
|
|
14
17
|
|
|
15
18
|
## 适用场景
|
|
16
19
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
20
|
+
- 接入微信公众号 JSAPI 支付(需 openid/WeixinJSBridge)
|
|
21
|
+
- 接入支付宝 H5(WAP 离线签名表单)
|
|
19
22
|
- 接入 Stripe(Checkout Session)
|
|
20
23
|
- 支付回调验签与通知处理
|
|
21
24
|
- 订单查询与退款
|
|
22
|
-
-
|
|
25
|
+
- 客户端调起支付(微信内置浏览器 / H5 跳转)
|
|
23
26
|
|
|
24
27
|
## 使用步骤
|
|
25
28
|
|
|
@@ -33,21 +36,32 @@ wechat:
|
|
|
33
36
|
apiV3Key: ''
|
|
34
37
|
privateKey: ''
|
|
35
38
|
serialNo: ''
|
|
36
|
-
|
|
39
|
+
platformKeys: # 必填,按通知 Wechatpay-Serial 精确选择受信平台证书/公钥
|
|
40
|
+
PUB_KEY_ID_123: '' # 平台公钥 PEM;证书使用其真实序列号作为 key
|
|
37
41
|
|
|
38
42
|
alipay:
|
|
39
43
|
appId: ''
|
|
44
|
+
sellerId: '' # 必填,验签后核对 seller_id
|
|
40
45
|
privateKey: ''
|
|
41
46
|
alipayPublicKey: ''
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
returnUrl: 'https://app.example/payment/return' # 必填 HTTPS,不能作为付款证明
|
|
48
|
+
signType: RSA2 # 可选,默认 RSA2
|
|
49
|
+
sandbox: false # 可选,默认 false
|
|
44
50
|
|
|
45
51
|
stripe:
|
|
46
52
|
secretKey: ''
|
|
47
53
|
webhookSecret: ''
|
|
48
|
-
|
|
54
|
+
successUrl: 'https://app.example/payment/success?session_id={CHECKOUT_SESSION_ID}' # 必填
|
|
55
|
+
cancelUrl: 'https://app.example/payment/cancel' # 可选
|
|
56
|
+
webhookToleranceSeconds: 300 # 可选,默认 300 秒
|
|
49
57
|
```
|
|
50
58
|
|
|
59
|
+
Stripe 的 secretKey 必须为 sk/rk 的 test/live 服务端密钥;受限密钥须允许读取账户。只实现 payment + hosted_page + tradeType=h5,必填 successUrl、可选 cancelUrl 均来自服务端配置且必须为 HTTP(S)。不支持 embedded/elements 或 App/JSAPI/Native 等支付场景,不向客户端提供改变模式/跳转地址的接口。API 固定为 2026-09-30.endive;启用前初始化共享 reldb,不能给不同节点配置各自的 SQLite 文件。
|
|
60
|
+
|
|
61
|
+
内置创建模式仅保留上表组合;公开 `TradeType` 为 `jsapi | h5`,App、Native、小程序及其它组合均在请求渠道前返回 `UNSUPPORTED_TRADE_TYPE`。微信 JSAPI 必须提供当前公众号的 `userId`(openid),微信与支付宝仅接受 CNY。微信下单缺失有效 `prepay_id` 时失败。支付宝 `createOrder` 只生成已签名的页面请求参数,不表示渠道已受理或已付款;返回 `{ gateway, fields }`,浏览器向固定正式/沙箱网关 POST 全部签名字段,不解析 HTML,也不接收任意 action。配置 `alipay.returnUrl` 必填且为 HTTPS,`HAI_PAYMENT_ALIPAY_RETURNURL` 可覆盖;回跳不代表付款成功。客户端先检查 `success`,再检查 `data.invoked`;微信桥接对象未就绪时 `invoked=false`,已调起后的取消回调也不代表付款。
|
|
62
|
+
|
|
63
|
+
协议依据:[微信 JSAPI 下单](https://pay.wechatpay.cn/doc/v3/merchant/4012791897)、[JSAPI 调起](https://pay.wechatpay.cn/doc/v3/merchant/4012791857)、[支付宝官方 Node SDK pageExecute](https://github.com/alipay/alipay-sdk-nodejs-all#pageexecute-示例代码)。定向测试验证请求/签名形状、真实 RSA 验签和 jsdom 表单提交;未使用真实商户凭据、渠道沙箱账户或手机设备,不能据此宣称已完成真实支付验收。
|
|
64
|
+
|
|
51
65
|
约定环境变量优先于 YAML,例如 `HAI_PAYMENT_WECHAT_APIV3KEY`、
|
|
52
66
|
`HAI_PAYMENT_STRIPE_WEBHOOKSECRET`。生产密钥只放环境或密钥管理器。
|
|
53
67
|
|
|
@@ -63,11 +77,14 @@ const initResult = await payment.init({
|
|
|
63
77
|
apiV3Key: 'your-api-key-v3',
|
|
64
78
|
privateKey: '-----BEGIN RSA PRIVATE KEY-----...',
|
|
65
79
|
serialNo: 'CERT_SERIAL_NO',
|
|
80
|
+
platformKeys: { PUB_KEY_ID_123: '-----BEGIN PUBLIC KEY-----...' },
|
|
66
81
|
},
|
|
67
82
|
alipay: {
|
|
68
83
|
appId: '2021000000000000',
|
|
84
|
+
sellerId: '2088000000000000',
|
|
69
85
|
privateKey: '-----BEGIN RSA PRIVATE KEY-----...',
|
|
70
86
|
alipayPublicKey: '-----BEGIN PUBLIC KEY-----...',
|
|
87
|
+
returnUrl: 'https://app.example/payment/return',
|
|
71
88
|
},
|
|
72
89
|
})
|
|
73
90
|
if (!initResult.success) {
|
|
@@ -106,13 +123,26 @@ const result = await payment.handleNotify('wechat', {
|
|
|
106
123
|
})
|
|
107
124
|
|
|
108
125
|
if (result.success) {
|
|
109
|
-
//
|
|
110
|
-
//
|
|
126
|
+
// success 仅表示验签解析成功;Stripe completed/unpaid 仍是 pending。
|
|
127
|
+
// 在共享数据库事务中核对渠道、订单、amount、currency,按渠道/商户命名空间持久去重 eventId。
|
|
128
|
+
// 仅 status=paid 可履约;乱序 pending/failed 不得回退已支付/已退款订单。
|
|
111
129
|
}
|
|
112
130
|
```
|
|
113
131
|
|
|
132
|
+
Stripe 只消费 payment-mode Checkout Session 的 `completed`、`async_payment_succeeded`、`async_payment_failed`、`expired` 事件,其它事件或缺失订单号/金额/币种返回 `NOTIFY_PARSE_FAILED`。结果的 `eventId` 用于持久去重,`currency` 为大写,`transactionId` 为 Session ID。只有 paid 结果返回 `paidAt`,取成功事件的 created 时间;Provider 不维护业务订单或进程内去重集合。
|
|
133
|
+
|
|
134
|
+
所有 Provider(含自定义实现)都必须返回 eventId/currency:微信 id、支付宝 notify_id、Stripe event.id;支付宝 currency 固定 CNY。微信只处理 TRANSACTION.SUCCESS/transaction,验签后继续校验 appid/mchid,按 Wechatpay-Serial 精确选择 platformKeys 项,检查证书序列号及有效期、严格整数签名时钟与当前节点相差不超过 300 秒。轮换期间所有节点同时保存新旧项;未知 serial 不能自动下载并信任。支付宝核对 app_id/seller_id/配置签名算法,拒绝重复表单字段,按北京时间严格解析真实日期;notify_id 跨日重试不变,不能对原始通知时间套五分钟有效期。支付宝请求使用 Asia/Shanghai 和 POST form body,不能把签名/订单内容放 URL。Stripe 按密钥校验 test/live,支持轮换期多个 v1 签名,只支持当前账户,拒绝 Connect/组织 context 事件。
|
|
135
|
+
|
|
136
|
+
接入应用应把“业务订单 pending→paid 的条件更新 + 唯一 eventId 记录 + 唯一履约 outbox”放进共享数据库同一个事务;所有 HaiResult 失败必须抛错使 reldb.tx.wrap 回滚,数据库提交后才应答渠道成功。条件须核对已存渠道、金额、币种和交易号,重复已支付通知或晚到 pending 不重复履约,已退款状态也不能倒退。订单号不得删除后复用,切换商户不能复用旧业务订单命名空间。外部发货等由 outbox 消费者携带稳定 outbox ID 幂等调用,不能在数据库事务中直接调用后宣称跨系统只执行一次。
|
|
137
|
+
|
|
114
138
|
### 5. 查询订单
|
|
115
139
|
|
|
140
|
+
Stripe 创建操作将 orderNo 与 Session ID 持久绑定,查单/退款通过该 ID 读取并核对订单号、金额和币种,不使用 metadata 列表首项猜测。metadata.orderNo 为保留字段。orderNo/refundNo 的请求参数不能在重试时改变,渠道 POST 使用稳定 Idempotency-Key。
|
|
141
|
+
|
|
142
|
+
Stripe closeOrder 会通过可信映射读取 Session,只有 open/unpaid 才调用 expire;已 expired 幂等成功,complete/paid 失败。并发关闭或响应丢失后必须重读确认 expired,不能把“已发送请求”当关闭成功。successUrl/cancelUrl 的页面跳转不代表已付款或已经关单,业务仍以可信状态为准。
|
|
143
|
+
|
|
144
|
+
响应丢失或落库失败会保留未知记录,只能在数据库时钟的 23 小时安全窗口内沿用同一键重试;窗口之外先核对渠道结果并修复记录,不删除后重发。成功结果长期保留,不按缓存 TTL 清理。Stripe 仅保存渠道操作结果,宿主业务订单、回调去重与履约事务仍需自己实现。
|
|
145
|
+
|
|
116
146
|
```typescript
|
|
117
147
|
const result = await payment.queryOrder('wechat', 'ORDER_20250101_001')
|
|
118
148
|
|
|
@@ -123,6 +153,8 @@ if (result.success) {
|
|
|
123
153
|
|
|
124
154
|
### 6. 退款
|
|
125
155
|
|
|
156
|
+
支付宝查单/退款/关单会验证方法对应的 envelope、字符串 code=10000、关键字段和订单号;HTTP 200 业务错误返回对应操作的 HaiResult 失败。错误 cause 不含原始响应或 msg/sub_msg。退款 `success: true` 表示请求受理,仍须检查 data.status:fund_change=Y 才为 success,N/缺失为 processing,需自行通过渠道退款查询确认(本模块没有退款查询 API)。refund_fee 是累计退款额,refundId 是原交易号,以 refundNo 区分本次退款且重试保持参数不变。关单先检查 result.success 再更新业务订单。
|
|
157
|
+
|
|
126
158
|
```typescript
|
|
127
159
|
const result = await payment.refund('wechat', {
|
|
128
160
|
orderNo: 'ORDER_20250101_001',
|
|
@@ -139,46 +171,53 @@ const result = await payment.refund('wechat', {
|
|
|
139
171
|
import { invokePayment } from '@h-ai/payment/client'
|
|
140
172
|
|
|
141
173
|
// 前端拿到服务端返回的 PaymentOrder 后调起支付
|
|
174
|
+
if (!orderResult.success)
|
|
175
|
+
throw new Error(orderResult.error.message)
|
|
142
176
|
const payResult = await invokePayment(orderResult.data)
|
|
177
|
+
if (!payResult.success)
|
|
178
|
+
throw new Error(payResult.error.message)
|
|
179
|
+
if (!payResult.data.invoked)
|
|
180
|
+
throw new Error(payResult.data.message)
|
|
143
181
|
```
|
|
144
182
|
|
|
145
183
|
## 核心 API(服务端)
|
|
146
184
|
|
|
147
|
-
| API
|
|
148
|
-
|
|
|
149
|
-
| `payment.init(config)`
|
|
150
|
-
| `payment.close()`
|
|
151
|
-
| `payment.createOrder(providerName, input)`
|
|
152
|
-
| `payment.handleNotify(providerName, request)`
|
|
153
|
-
| `payment.queryOrder(providerName, orderNo)`
|
|
154
|
-
| `payment.refund(providerName, input)`
|
|
155
|
-
| `payment.closeOrder(providerName, orderNo)`
|
|
156
|
-
| `payment.getProvider(name)`
|
|
157
|
-
| `payment.registerProvider(provider)`
|
|
185
|
+
| API | 用途 | 返回值 |
|
|
186
|
+
| --------------------------------------------- | ------------------- | -------------------------------- |
|
|
187
|
+
| `payment.init(config)` | 初始化 | `HaiResult<void>` |
|
|
188
|
+
| `payment.close()` | 关闭 | `void` |
|
|
189
|
+
| `payment.createOrder(providerName, input)` | 创建订单 | `HaiResult<PaymentOrder>` |
|
|
190
|
+
| `payment.handleNotify(providerName, request)` | 处理回调 | `HaiResult<PaymentNotifyResult>` |
|
|
191
|
+
| `payment.queryOrder(providerName, orderNo)` | 查询订单 | `HaiResult<OrderStatus>` |
|
|
192
|
+
| `payment.refund(providerName, input)` | 退款 | `HaiResult<RefundResult>` |
|
|
193
|
+
| `payment.closeOrder(providerName, orderNo)` | 关闭订单 | `HaiResult<void>` |
|
|
194
|
+
| `payment.getProvider(name)` | 获取 Provider | `PaymentProvider \| undefined` |
|
|
195
|
+
| `payment.registerProvider(provider)` | 注册自定义 Provider | `void` |
|
|
158
196
|
|
|
159
197
|
## 客户端 API
|
|
160
198
|
|
|
161
|
-
| API | 用途 | 返回值
|
|
162
|
-
| ------------------------ | -------- |
|
|
199
|
+
| API | 用途 | 返回值 |
|
|
200
|
+
| ------------------------ | -------- | -------------------------------- |
|
|
163
201
|
| `invokePayment(options)` | 调起支付 | `HaiResult<InvokePaymentResult>` |
|
|
164
202
|
|
|
165
203
|
## 错误码 — `HaiPaymentError`
|
|
166
204
|
|
|
167
|
-
| 错误码
|
|
168
|
-
|
|
169
|
-
| `HaiPaymentError.CREATE_ORDER_FAILED`
|
|
170
|
-
| `HaiPaymentError.QUERY_ORDER_FAILED`
|
|
171
|
-
| `HaiPaymentError.REFUND_FAILED`
|
|
172
|
-
| `HaiPaymentError.CLOSE_ORDER_FAILED`
|
|
173
|
-
| `HaiPaymentError.NOT_INITIALIZED`
|
|
174
|
-
| `HaiPaymentError.SIGN_FAILED`
|
|
175
|
-
| `HaiPaymentError.PROVIDER_NOT_FOUND`
|
|
176
|
-
| `HaiPaymentError.INVALID_AMOUNT`
|
|
177
|
-
| `HaiPaymentError.
|
|
178
|
-
| `HaiPaymentError.
|
|
179
|
-
| `HaiPaymentError.
|
|
180
|
-
| `HaiPaymentError.
|
|
181
|
-
| `HaiPaymentError.
|
|
205
|
+
| 错误码 | code | 说明 |
|
|
206
|
+
| ---------------------------------------- | ----------------- | ------------------------- |
|
|
207
|
+
| `HaiPaymentError.CREATE_ORDER_FAILED` | `hai:payment:001` | 创建订单失败 |
|
|
208
|
+
| `HaiPaymentError.QUERY_ORDER_FAILED` | `hai:payment:002` | 查询订单失败 |
|
|
209
|
+
| `HaiPaymentError.REFUND_FAILED` | `hai:payment:003` | 退款失败 |
|
|
210
|
+
| `HaiPaymentError.CLOSE_ORDER_FAILED` | `hai:payment:004` | 关闭订单失败 |
|
|
211
|
+
| `HaiPaymentError.NOT_INITIALIZED` | `hai:payment:010` | 模块未初始化 |
|
|
212
|
+
| `HaiPaymentError.SIGN_FAILED` | `hai:payment:020` | 签名失败 |
|
|
213
|
+
| `HaiPaymentError.PROVIDER_NOT_FOUND` | `hai:payment:030` | Provider 未注册 |
|
|
214
|
+
| `HaiPaymentError.INVALID_AMOUNT` | `hai:payment:040` | 金额无效 |
|
|
215
|
+
| `HaiPaymentError.UNSUPPORTED_TRADE_TYPE` | `hai:payment:041` | 渠道不支持该模式 |
|
|
216
|
+
| `HaiPaymentError.INVALID_INPUT` | `hai:payment:042` | openid/币种等下单参数无效 |
|
|
217
|
+
| `HaiPaymentError.NOTIFY_VERIFY_FAILED` | `hai:payment:050` | 回调验签失败 |
|
|
218
|
+
| `HaiPaymentError.NOTIFY_PARSE_FAILED` | `hai:payment:051` | 回调解析失败 |
|
|
219
|
+
| `HaiPaymentError.INVOKE_WEB_FAILED` | `hai:payment:060` | Web 端调起支付失败 |
|
|
220
|
+
| `HaiPaymentError.CONFIG_ERROR` | `hai:payment:070` | 配置无效 |
|
|
182
221
|
|
|
183
222
|
## Provider 模式
|
|
184
223
|
|
|
@@ -237,7 +276,7 @@ export const POST = kit.handler(async ({ request, locals }) => {
|
|
|
237
276
|
3. 服务端 → 客户端:返回 providerData(含调起参数)
|
|
238
277
|
4. 客户端:invokePayment(providerData) 调起支付
|
|
239
278
|
5. 支付平台 → 服务端:异步回调通知
|
|
240
|
-
6. 服务端:handleNotify()
|
|
279
|
+
6. 服务端:handleNotify() 验签解析,业务层持久核对并幂等更新订单状态
|
|
241
280
|
7. 客户端:轮询或回调查询订单状态
|
|
242
281
|
```
|
|
243
282
|
|
|
@@ -1,104 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-pr-review
|
|
3
|
-
description: "审查 PR
|
|
3
|
+
description: "审查 PR、提交或分支的实际差异、调用影响、需求符合性、风险与验证证据,评估交付准备度。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# hai-pr-review
|
|
6
|
+
# hai-pr-review
|
|
7
7
|
|
|
8
8
|
## 能力契约
|
|
9
9
|
|
|
10
10
|
| 项目 | 契约 |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| 能力 |
|
|
13
|
-
| 适用场景 |
|
|
14
|
-
| 输入 |
|
|
15
|
-
| 输出 |
|
|
16
|
-
| 限制 |
|
|
12
|
+
| 能力 | 对固定 PR/提交/分支差异进行审查,核对需求、调用影响与交付条件 |
|
|
13
|
+
| 适用场景 | 用户要求差异审查、开发后的交付自查或 PR 准备度评估 |
|
|
14
|
+
| 输入 | 实际 base/head、需求与计划、完整 diff、相关实现和 CI/运行证据 |
|
|
15
|
+
| 输出 | 可定位发现、实际覆盖范围、验证缺口和交付结论 |
|
|
16
|
+
| 限制 | 只读审查不自动改代码或发布评论;已有修复授权持续有效。审查不赋予远端推送、合并或发布授权。 |
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## 审查路径
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
1. 读取仓库入口和 PR/任务目标,确认比较对象。按 [差异审查](../hai-app-review/differential.md) 固定 base/head、列全文件与重命名,说明 staged/unstaged 和截断限制。
|
|
21
|
+
2. 先将需求、验收与实际 diff 对照,检查遗漏、范围扩张和陈旧模板断言。代码符合性、安全、框架复用和生命周期沿 [hai-app-review](../hai-app-review/SKILL.md);界面涉及交互或布局时调用已交付的 hai-ui-review,纯 API 不需要它。
|
|
22
|
+
3. 按风险读取调用方、类型、配置、迁移、历史和测试;删除校验、事务、资源归属检查时核验真实替代约束。两行权限改动仍可能需要深入分析。
|
|
23
|
+
4. 核对测试证明了什么、是否对应最终代码和真实环境。CI required checks 查询实际状态:成功、失败、运行中、未执行或无权读取分别报告。本地通过不能替代远端 CI;分支规则不可读取时保留该限制。
|
|
24
|
+
5. 对每条发现复核触发路径和反证,合并同根因问题;按 [工作交接契约](../hai-spec-implement/handoff.md) 报告。已授权修复时完成本次范围修复,重验受影响项并针对最终 head 更新结论。
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
- 审查 Pull Request 的 scope、风险、验证方式、issue 关联与合并准备度
|
|
24
|
-
- 判断 AI Review 是 advisory 还是 blocking,以及是否需要人工复核
|
|
25
|
-
- 新增或修改 `.github/pull_request_template.md`
|
|
26
|
-
- 配置 CODEOWNERS、required review、branch protection
|
|
27
|
-
- 设计 AI PR Review 方案
|
|
28
|
-
- 选择 Copilot、CodeRabbit、reviewdog、DangerJS、Claude Code Action、Codex Action 等工具
|
|
26
|
+
## 交付条件
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
- 需求与验收有完成/已有满足/排除/阻塞的实际结论,公共变化同步服务、客户端、迁移、配置、UI/i18n、测试、README 和 skills。
|
|
29
|
+
- PR 描述围绕具体问题、最终行为、验证和剩余边界,按仓库规则关联 issue 或明确需求与验收。要求披露 AI 使用时说明辅助范围与实际复核,不粘贴聊天原文、凭据或私有上下文。
|
|
30
|
+
- 适用门禁对最终代码有效通过;已确认的阻塞发现已解决;项目所需人工审批和 required checks 状态真实可查。未完成项明确列出,不能仅由 AI 建议数量判定可合并。
|
|
31
|
+
- workflow、依赖或发布流程变化按 [hai-ci](../hai-ci/SKILL.md) 检查来源、权限、secret 和执行边界。不得在不可信 PR 代码执行环境暴露密钥。
|
|
31
32
|
|
|
32
|
-
##
|
|
33
|
+
## 报告格式
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
先写实际结论和比较范围,再按严重程度列可行动发现;每条包含文件/当前差异行、触发条件、后果、依据和最小验收。最后列已检查路径、CI/本地/浏览器的实际证据和未验证边界。无发现时直接说明本次范围未确认问题,不补泛泛建议凑数。
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
- 具体 workflow、权限、secret scan 与 required check 配置由 `hai-ci` 定义。
|
|
38
|
-
- 测试覆盖策略与失败用例设计由 `hai-app-tests` 定义。
|
|
39
|
-
|
|
40
|
-
### 第 2 层:结构化人工审查(必须)
|
|
41
|
-
|
|
42
|
-
- scope 是否清晰且与 issue 对齐
|
|
43
|
-
- 测试是否覆盖成功路径、失败路径、边界条件
|
|
44
|
-
- 本次需求是否复用适用框架能力/feature/配置,自定义或新依赖是否有已安装版本的缺口依据(见 [development.md](../hai-app-create/development.md))
|
|
45
|
-
- 是否符合实际返回契约、i18n、日志、分层、当前 lint 风格,以及输入/越权/失败等适用行为验收;不能以导入包数量或 CI 全绿替代代码与安全审查
|
|
46
|
-
- 是否引入密钥、权限扩大、workflow 风险或两仓漂移
|
|
47
|
-
|
|
48
|
-
### 第 3 层:AI Review(默认 advisory)
|
|
49
|
-
|
|
50
|
-
- AI Review 默认只评论、不阻塞 merge。
|
|
51
|
-
- 只有在 CI 稳定、误报可控、团队明确同意后,才考虑将特定静态规则设为 blocking。
|
|
52
|
-
- AI 建议须用实现与测试核实;执行合并必须有用户授权并满足仓库规则。
|
|
53
|
-
|
|
54
|
-
## AI-assisted PR 要求
|
|
55
|
-
|
|
56
|
-
按仓库 PR 模板说明;要求披露 AI 使用时包含:
|
|
57
|
-
|
|
58
|
-
- 是否使用 AI 辅助
|
|
59
|
-
- AI 负责了哪部分:搜索、实现、测试、review、文档
|
|
60
|
-
- 人类如何验证:命令、截图、日志或 reviewer 重点
|
|
61
|
-
- 是否复制了聊天记录;若有,只能复制脱敏后的结论,禁止包含 token、cookie、私有 URL、账号密码
|
|
62
|
-
|
|
63
|
-
## Issue 关联与上下文交接
|
|
64
|
-
|
|
65
|
-
- 按当前仓库规则关联 issue;没有 issue 时清楚描述需求与验收。
|
|
66
|
-
- PR scope 必须与 issue 的目标、验收标准和明确不做事项一致。
|
|
67
|
-
- AI-assisted PR 必须说明 AI 负责了哪些阶段,以及人类如何复核。
|
|
68
|
-
- 多人或多 agent 协作时,PR 描述必须列出 reviewer focus,避免重复 review。
|
|
69
|
-
|
|
70
|
-
## 自动 Review 工具选择
|
|
71
|
-
|
|
72
|
-
| 工具 | 推荐定位 | 是否适合 blocking |
|
|
73
|
-
| --- | --- | --- |
|
|
74
|
-
| reviewdog | 把 lint/static analyzer 结果贴到 PR | 适合,前提是规则稳定 |
|
|
75
|
-
| DangerJS | PR 元数据、模板、标签、变更范围规则 | 适合,前提是规则明确 |
|
|
76
|
-
| GitHub Copilot Code Review | 通用 AI 代码建议 | 默认 advisory |
|
|
77
|
-
| CodeRabbit | 上下文型 AI review | 默认 advisory |
|
|
78
|
-
| Claude Code Action | 定制化深度 review / 修改建议 | 默认 advisory,严格限制权限 |
|
|
79
|
-
| Codex Action | 定制化 review / 修复建议 | 默认 advisory,严格控制 API key |
|
|
80
|
-
|
|
81
|
-
## 权限与安全红线
|
|
82
|
-
|
|
83
|
-
- ❌ 不在 `pull_request_target` 中运行 AI agent 读取 PR 代码和 secrets。
|
|
84
|
-
- ❌ 不给 AI Review workflow 默认 `contents: write`、`pull-requests: write`,除非只用于评论且触发源可信。
|
|
85
|
-
- ❌ 不因收到审查任务就擅自合并 PR。
|
|
86
|
-
- ❌ 不把私有提示词、聊天原文、token、cookie、连接串写入 PR。
|
|
87
|
-
- ✅ AI Review 的 prompt 必须限制任务范围、禁止执行 secrets、要求引用具体文件/行。
|
|
88
|
-
|
|
89
|
-
## PR Review 检查清单
|
|
90
|
-
|
|
91
|
-
- [ ] PR 关联 issue,scope 单一
|
|
92
|
-
- [ ] PR 模板完整填写
|
|
93
|
-
- [ ] CI required checks 全绿
|
|
94
|
-
- [ ] 新行为有测试,失败路径有覆盖
|
|
95
|
-
- [ ] 框架能力选择、代码风格与适用安全边界已由 `hai-app-review` 覆盖,有行为证据或明确未验证项
|
|
96
|
-
- [ ] 文档 / skill / README 与代码同步
|
|
97
|
-
- [ ] workflow 权限或 secrets 变化已由 `hai-ci` 审查
|
|
98
|
-
- [ ] AI-assisted 内容已由人类复核
|
|
99
|
-
|
|
100
|
-
## 相关 Skills
|
|
101
|
-
|
|
102
|
-
- `hai-ci` — CI/CD 与 workflow 安全
|
|
103
|
-
- `hai-app-review` — 应用代码质量与框架复用审查
|
|
104
|
-
- `hai-framework-sync` — 框架升级差异与双仓同步审查
|
|
37
|
+
仅要求审查时在报告处结束;已获修复授权则修复并复核。提交评论、创建 PR、远端推送、合并与发布按用户的实际授权继续,遵守项目规则。
|