主题
支付 → Hermes 容器 → 微信绑定 流程文档
目标态(intended design),不是当前 backend 实现。当前代码与本文档存在差异,差异点见底部「现状 gap」章节。前端 v3 重画按本文档目标态写文案。
一、流程总览(4 阶段,目标态)
阶段 1 — 支付回调
└─ 微信支付 webhook → order.status = PAID
阶段 2 — Spawn Hermes 容器(与客户绑定)
├─ 创建 hermesInstance 行,绑定 userId
├─ 创建 subscription 行
├─ NewAPI 发 LLM token,写入 instance.llmApiKey
└─ 执行 spawn-profile.sh
├─ 起 docker 容器(Hermes 进程 + profile dir)
├─ profile 与 instance 一一对应(profileName = instance.id 或 user 标识)
├─ 容器暴露 baseUrl + hostPort
└─ instance.status = READY_TO_BIND
阶段 3 — 微信绑定(QR 来自 Hermes 容器,不是 iLink)
├─ 前端 StatusPage 调 backend /services/instance/:id/wechat-qr
├─ backend 转发到 Hermes 容器 baseUrl/wechat/qr
├─ Hermes 容器返回 QR + token(容器自己生成)
├─ 前端展示 QR,轮询 backend /wechat-status
├─ backend 转发到 Hermes 容器 baseUrl/wechat/status?qrcode=...
├─ 用户微信扫码 → Hermes 确认 → 返回 confirmed
└─ instance.status = READY,绑定信息保存在容器内
阶段 4 — 服务可用
└─ 用户在微信里与 Hermes bot 对话二、状态机(hermesInstance.status)
| state | 含义 | 进入方式 | 用户能看到什么 |
|---|---|---|---|
PROVISIONING | 支付完,正在 spawn 容器 | 阶段 2 起始 | "支付成功,正在创建专属 Agent..." |
READY_TO_BIND | 容器已起,等微信扫码 | spawn-profile.sh 成功 | "请扫描下方二维码绑定微信" + QR |
BINDING | 用户扫了码,等确认 | iLink/Hermes 返回 scanned | "请在微信中确认绑定" |
READY | 绑定完成,服务可用 | Hermes 返回 confirmed | "Agent 已上线,回到微信对话" |
ERROR | spawn 失败 or 绑定失败 | spawn-profile.sh 报错 / 超时 | 报错 + rebind 按钮 |
旧状态 PENDING_BIND 含义模糊("已建账未绑定" vs "容器已起未绑定"),目标态删除。
三、前端文案对应(v3 Art Deco 重画用)
PurchasePage — 支付成功 banner
✅ 支付成功
账户已创建,正在为你启动专属 Agent...
[前往激活 →] → navigate('/dashboard/status')StatusPage — 按 status 分支
| status | 主区文案 | 主区元素 | CTA |
|---|---|---|---|
PROVISIONING | "Agent 启动中(约 30 秒)" | loading spinner | — |
READY_TO_BIND | "扫码激活微信" | Hermes 出的 QR | "刷新二维码" |
BINDING | "请在微信中确认" | 模糊化 QR | — |
READY | "Agent 已上线" | 微信链接按钮 | "回到微信对话" |
ERROR | "启动失败:{lastError}" | — | "重试" → rebind |
注意:StatusPage 的 WeixinState enum 现在是 idle/loading/qr/confirmed/provisioning/ready/failed/error,与目标态 status 不完全对齐。重画时按目标态状态机写,源 enum 暂不改。
四、关键设计原则
QR 是容器出的,不是外部服务出的
- Hermes 容器启动时即生成 WeChat 绑定 QR
- backend 只做 reverse proxy,不持有 iLink 凭证
容器先起,绑定后做
- 付款后立即 spawn,不等绑定
- 用户付完到看见 QR 之间只隔一个 spawn 耗时(约 30s)
- 不需要在「拿 QR」这步阻塞用户
profile = instance 一一对应
- 每个付费用户一个 Hermes profile dir
- profileName 用 instance.id 作为唯一键
- profile 包含 LLM key + 用户上下文 + 微信绑定(绑定后)
绑定信息存在容器内
- 微信 channel / token 由 Hermes 容器持有,不需要 backend 存
- rebind 时 backend 停容器 + 重 spawn,让容器重新出 QR
五、API 路由(目标态)
| Method | Path | 行为 |
|---|---|---|
| POST | /orders | 创建订单 |
| POST | /payments/wechat/qrcode | 出微信支付 QR |
| GET | /orders/:id/status | 查支付状态 |
| POST | /payments/wechat/dev-mock-paid | DEV-only 模拟回调 |
| GET | /services/status | 查当前 subscription + instance.status |
| GET | /services/instance/:id/wechat-qr | 代理到 Hermes 容器:${baseUrl}/wechat/qr |
| GET | /services/instance/:id/wechat-status | 代理到 Hermes 容器:${baseUrl}/wechat/status?qrcode=... |
| POST | /services/instance/:id/rebind | 停容器 + 重 spawn → 新 QR |
六、现状 gap(当前 backend 与目标态差异)
仅记录,本次前端重画不动 backend。等前端跑通后再单独开 PR 改 backend。
gap 1: QR 来自 iLink 而不是 Hermes
- 现状:
service-runtime.service.ts:54-60getWeixinQr()直接axios.get('https://ilinkai.weixin.qq.com/ilink/bot/get_bot_qrcode') - 目标:proxy 到
${instance.baseUrl}/wechat/qr - 改动:删 ILINK_BASE 常量 + iLink 调用,改为读 instance + 转发到容器
- 依赖:Hermes 端必须暴露
/wechat/qr路由
gap 2: spawn 时机错位
- 现状:spawn 发生在 BIND_CONFIRMED 之后(
provisioning.service.ts:119spawnAfterBind) - 目标:spawn 发生在 PAID 之后(
provisionPaidOrder内联 spawn) - 改动:
provisionPaidOrder末尾直接调spawnHermesProfilePublic- 删
spawnAfterBind或保留作为重启路径(rebind 复用) onBindConfirmed不再调spawnAfterBind,只更新 status = READY
gap 3: 状态机不一致
- 现状:
PENDING_BIND → BIND_CONFIRMED → PROVISIONING → READY - 目标:
PROVISIONING → READY_TO_BIND → BINDING → READY - 改动:Prisma schema 改 enum;写 migration;service 代码改状态转移;前端 StatusPage 状态分支同步
gap 4: spawn-profile.sh 入参依赖绑定信息
- 现状:
spawnHermesProfilePublic接受weixinChannel/weixinToken作为 env 注入 - 目标:spawn 时还没绑定,这两个参数应为空;容器自己处理绑定
- 改动:
provision-worker.service.ts:147-148删掉这两个入参的传递- spawn-profile.sh 内不再依赖
WEIXIN_HOME_CHANNEL/WEIXIN_TOKEN - Hermes 启动时自己生成 QR 流程
gap 5: NewAPI token 创建在 spawn 前 OK
- 现状:
provisionPaidOrder内创建 NewAPI token → 存 instance.llmApiKey - 目标:相同,spawn 时把 llmApiKey 注入到容器 env
- 无 gap
七、迁移顺序(backend 改造 PR 拆分建议)
- PR-A:Hermes 端加
/wechat/qr+/wechat/status路由(其它仓库) - PR-B:backend service-runtime 改 proxy(删 iLink 调用)
- PR-C:backend provisioning 改 spawn 时机(PAID → spawn 一步到位)
- PR-D:Prisma schema status enum 改造 + 状态机 service 改写
- PR-E:前端 StatusPage 状态分支按新 enum 改
本次前端 v3 重画 = PR-E 的视觉部分。不动 backend,文案按目标态写,行为暂用现有 enum 兜底(差异点用注释标 // TODO: align with target state machine)。
最后更新:2026-05-18 作者:v3 Art Deco redesign session 关联文档:docs/product-doc-v3.md(marketing 文案)、backend/src/modules/provisioning/(现状代码)