Files
vmall/README.md
T
Chengdong Zhang a7cd392bd4 feat(payments): configurable checkout payments — wallet + Stripe crypto, original-method refunds (add-configurable-payments)
- payment domain: payments/memberships/attempts/receipts/refunds/commands/events, legacy-demo provenance (0024), checkout header-hash idempotency (0025)
- wallet collection all-or-none across shop orders; group-wide cancellation; checkout cutover, status-only payOrder removed
- Stripe crypto-only Checkout adapter, raw-body HMAC webhooks, durable command worker with leases
- original-method refunds with receipt/allocation budgets; refund_pending/refund_failed aftersale states
- admin channel CRUD/revisions/audit/verify + operations list/detail/reconcile; secret-ref allowlist (VMALL_PAYMENT_*), env-fixed demo/sandbox/live
- mall/mobile checkout pay surfaces, admin console pages; shared contract updated
- pre-launch leftovers recorded in docs/TBD-payments.md

cargo test -p vmall-api green twice; four frontend builds green; browser smoke: mall wallet checkout paid, admin channels/operations verified
2026-10-15 16:14:28 +08:00

112 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VMall — B2B2C 商城 MVP
前后端分离的多商户(B2B2C)商城。一个 Rust 后端,三个 Nuxt 前端,Postgres + Redis(本地 Docker 提供),OpenSpec 驱动开发。
## 架构
```
apps/
api/ Rust 后端(axum 0.8 + sqlx + redis) http://localhost:8080/api
mall/ Nuxt 3 顾客商城 http://localhost:3000
shop-admin/ Nuxt 3 商户后台 http://localhost:3001
admin/ Nuxt 3 平台后台 http://localhost:3002
mobile/ Nuxt 3 移动 H5 商城 http://localhost:3003
packages/
shared/ @vmall/shared:TS 类型、API client、en/zh 语言包、共享样式
openspec/ OpenSpec 规范(specs/ 为归档后的能力规范)
docs/adr/ Architecture Decision Records(英文)
docs/tech-specs/ 内部技术规格(英文)
scripts/
seed-demo.mjs 演示数据脚本
```
技术要点:
- **金额**:整数最小单位(minor units)+ ISO 币种码存储,禁止浮点。`rate_to_base` 表示"1 基准币 = N 本币",换算四舍五入(half-up)。
- **多语言**:商品/店铺/分类/币种内容以 JSONB `{"en","zh"}` 存储;UI 文案在 `@vmall/shared` locales + 各应用 `locales-extra.ts`。
- **RBAC**:`platform_admin`、`shop_owner`、`shop_staff`(带 shop_id 作用域)、`customer`;JWT(24h)。
- **下单**:Redis 购物车 → 单事务结算,按店铺拆单,价格快照 + 扣库存;缺货整单 409 回滚;取消恢复库存。
- **发货单**:支持部分发货(校验剩余量),状态联动 fulfilling → shipped → completed。
- **发票**:每单最多一张有效发票;企业发票必填税号;商户开具生成发票号。
## 快速开始
前置:Docker 中的 Postgres(容器 `pg18`,postgres/postgres)与 Redis(容器 `rdb8`)已运行;Rust 1.98+、Node 22+、pnpm 10+。
```bash
# 1. 数据库(只需一次)
docker exec pg18 psql -U postgres -c "CREATE DATABASE vmall;" -c "CREATE DATABASE vmall_test;"
# 2. 安装依赖
pnpm install
# 3. 启动后端(自动执行 sqlx 迁移并播种平台管理员)
cargo run -p vmall-api
# 4. 播种演示数据(店铺、店主、4 个双语商品)
node scripts/seed-demo.mjs
# 5. 启动前端(另开终端)
pnpm dev:mall # :3000
pnpm dev:shop-admin # :3001
pnpm dev:admin # :3002
pnpm --filter @vmall/mobile dev # :3003
```
演示账号:
| 角色 | 账号 | 密码 | 应用 |
|---|---|---|---|
| 平台管理员 | admin@vmall.local | admin1234 | admin :3002 |
| 店主 | shop@vmall.local | shop12345 | shop-admin :3001 |
| 顾客 | customer@vmall.local | customer123 | mall :3000 |
## 测试与校验
```bash
cargo test -p vmall-api # 29 个集成测试(vmall_test + Redis)
pnpm --filter @vmall/mall build # 打包;注意:并不做类型校验
pnpm --filter @vmall/shop-admin build
pnpm --filter @vmall/admin build
openspec validate --all --strict # 规范校验
```
`nuxt build` 不做类型校验:`nuxt.config.ts` 未开启 `typescript.typeCheck`,也未安装 `vue-tsc`。构建通过只说明能打包。要补上这道关:加 `vue-tsc` + `typescript` 依赖、设 `typescript: { typeCheck: true }`,首次运行会暴露一批既有错误。在那之前,**能证明页面可用的是浏览器实测,而不是构建**。
## 商城的 mock 边界
`apps/mall` 通过 `apps/mall/plugins/api.ts` 的 `liveDomains` 按域选择适配器。当前真实后端域包括
catalog、currency、content、brands、shops、auth、account、cart、orders、shipments、invoices、
addresses、coupons、points、flashSales、groupBuying、favorites、aftersales、reviews、wallet、
merchantOnboarding、membership、messaging。
`merchantOnboarding` 是商家入驻域:只有 `submitMerchantApplication` 与
`getMyMerchantApplications` 两个方法走真实后端(admin 侧审核方法由 `apps/admin` 直接调用,
mall 不使用);把该域从 `liveDomains` 移除即回落到 `apps/mall/mock/api.ts` 的确定性 fixture。
`membership`(`getMembership`/`listGrowthLogs`)与 `messaging`(消息列表、已读、全部已读、删除、
未读数)同理各自独立可回滚;`messaging` 的未读数由页头徽标消费。
仍由页面直接读取 `~/mock/data` 的能力记录在 `docs/TBD-marketing.md`。营销 fixture 仍由
fixed-data 适配器使用,作为每个已迁移域的回滚实现。
- **fixed-data 适配器本身**:`Mock API adapter` 规范要求它仍能服务每个域,因此它是回滚路径,删除它会破坏回滚。
## 环境变量(后端)
| 变量 | 默认值 |
|---|---|
| `DATABASE_URL` | `postgres://postgres:postgres@127.0.0.1:5432/vmall` |
| `REDIS_URL` | `redis://127.0.0.1:6379/` |
| `JWT_SECRET` | `vmall-dev-secret-change-me` |
| `PORT` | `8080` |
| `JWT_TTL_SECS` | `86400` |
| `VMALL_PAYMENT_ENVIRONMENT` | `demo`(`demo`/`sandbox`/`live`,部署期固定,API 不可改) |
| `VMALL_PAYMENT_MALL_ORIGIN` / `VMALL_PAYMENT_MOBILE_ORIGIN` | 无;Stripe 跳转回流的商城/移动端源,仅允许配置值,钱包支付不需要 |
| `VMALL_PAYMENT_STRIPE_API_VERSION` | `2025-08-27.basil` |
| `VMALL_PAYMENT_STRIPE_API_BASE` | Stripe 固定主机;非 live 仅允许 loopback 覆盖(测试 fixture) |
| `VMALL_PAYMENT_WORKER_ENABLED` / `VMALL_PAYMENT_WORKER_POLL_MS` | `true` / 默认轮询间隔 |
| `VMALL_PAYMENT_*`(其余) | 支付渠道密钥引用白名单:渠道只存引用名,密钥本体只从匹配 `VMALL_PAYMENT_[A-Z0-9_]*` 的环境变量解析,永不入库或出现在审计中 |
支付要点:demo 环境启用演示充值并拒绝外部渠道;sandbox/live 需要独立的空数据库且无任何 demo 账务历史,否则启动拒绝。Stripe Webhook 端点为
`POST /api/payments/webhooks/stripe/{channel_id}`(原始请求体验签)。