# R3 订单闭环 + 支付接口占位(feat/r3-order-pay) **Goal:** 打通“设计清单 → 结算确认 → 创建订单 → 订单列表/详情 → 状态流转”的服务端闭环, 微信支付只留接口与配置位,不填真实密钥;支付不可用时流程能明确回到“待付款/支付未配置”。 **Architecture:** 前端 `api/order.ts` 承接下单与查询,`checkout/orders/orderDetail` 三个页面 改为服务端数据源;后端订单创建在事务内重算金额、快照地址、生成幂等的订单号并创建 PENDING 支付记录;`payments` 在密钥未配置时返回显式占位响应,不做假成功。 **Tech Stack:** Taro 3.6 + React 18 + TypeScript;NestJS 11 + Prisma/PostgreSQL + Redis/BullMQ(可选)。 ## 1. 本分支要开发的内容 ### 前端 `wechat_wc` | 文件 | 职责 | |---|---| | `src/utils/api/order.ts` | `createOrder/fetchOrders/fetchOrderDetail/payOrder`;请求只带商品项、地址 id、designListId,不带金额 | | `src/types/index.ts` | 服务端 Order 类型:`orderNo/status/totalAmount/items/addressSnapshot/createdAt/paidAt` | | `src/pages/checkout/index.tsx` | 从服务端读设计清单,选择地址(用 R2 的地址接口),提交下单;展示服务端总价;支付未配置时提示后回订单列表 | | `src/pages/orders/index.tsx` | 订单列表按状态 Tab 拉取;付款/确认收货等按钮调用对应接口或显示未配置 | | `src/pages/orderDetail/index.tsx` | 按订单 id 拉详情;地址快照、商品、状态条、物流占位 | | `src/utils/store/order.ts` | 标记 deprecated,仅保留为本地兜底,不再被 checkout 主流程调用 | | `src/pages/profile/index.tsx` | 状态统计改为基于服务端订单列表(R2/R3 合入后统一收尾) | ### 后端 `wxmp_backend` | 模块 | 内容 | |---|---| | `orders` | `POST /api/orders`:服务端按 Product/design-list 重算金额、事务创建订单+items+Payment、生成唯一 orderNo、快照地址 | | `orders` 查询 | `GET /api/orders`(状态筛选/分页)、`GET /api/orders/:id`(仅本人)、`PATCH /api/orders/:id/confirm`、`POST /api/orders/:id/cancel`(按需) | | `payments` | `POST /api/payments/:orderId/pay` 与 `POST /api/payments/notify` 保留签名;密钥为空时返回 `{ configured:false, message:'支付未配置' }` | | `wechat` | `createUnifiedOrder/verifyPayNotify` 保持占位,不填入假商户参数 | | `queue` | 可选:订单创建后入队定制任务,处理器先只更新 `CustomizationTask` 状态 | | `docs/api-contract-v1.md` | 订单创建请求/响应、状态机、金额字段精度、错误码、幂等键 | ### 词云项目 `wordcloud` 不参与本分支。 ## 2. 订单状态机 服务端 `OrderStatus` 与前端展示映射: | 后端 | 前端 Tab | 说明 | |---|---|---| | PENDING | 待付款 | 允许取消;支付未配置时长期停留 | | PAID | 待发货 | 由支付回调推进,占位期不出现 | | PROCESSING | 待发货 | 定制生产中 | | SHIPPED | 待收货 | 需要发货/物流数据,本分支可展示占位 | | COMPLETED | 已完成 | 用户确认收货推进 | | CANCELLED | 已取消 | 待付款状态用户取消 | 前端只读状态数组,不做状态转移判断;转移一律走后端接口或支付回调。 ## 3. 接口设计要点 创建订单请求: ```ts { designListId: string, addressId: string, items: [{ productId, quantity }] } ``` 服务端响应: ```ts { id: string, orderNo: string, status: "PENDING", totalAmount: number, items: [], addressSnapshot: {}, createdAt: string } ``` ## 4. 设计注意事项 ### DO - DO 订单金额、单价、总价全部由服务端计算,前端提交只给 `productId + quantity`。 - DO 创建订单使用 `prisma.$transaction`,订单、明细、支付记录任一步失败整体回滚。 - DO 给创建订单接口支持幂等键(客户端 `requestId` 或 `designListId` 防重复下单),双击提交只产生一单。 - DO 订单号 `orderNo` 唯一,生成规则包含日期与随机位并在冲突时重试。 - DO 收货地址在创建订单时直接快照到 `addressSnapshot`,订单创建后不再跟随地址变更。 - DO 支付占位返回结构化 `configured:false`,前端据此显示“暂不支持支付”而不是错误弹窗。 - DO 后端从配置读取微信支付密钥,密钥缺失时支付接口直接返回占位,不使用默认值或硬编码。 - DO 订单列表/详情都校验当前用户 openid,越权访问返回 403。 - DO 金额相关字段用 Decimal 精确类型持久化,序列化时统一成字符串或 number,避免浮点误差。 ### DON'T - DON'T 信任前端传的 `totalAmount`/`price`,即使前端为了展示计算过也一律忽略。 - DON'T 让客户端参数直接决定 `status`,状态只能由服务端接口和支付回调推进。 - DON'T 在代码里填占位商户号、密钥、证书路径;密钥缺失是合法运行状态而非 bug。 - DON'T 模拟支付成功,包括开发环境;未配置就是未配置,避免上线前“假流程”掩盖问题。 - DON'T 支付回调先解锁订单再验签;本分支只留入口,不实现任何未验签的落库逻辑。 - DON'T 在本分支删除 `store/order.ts`,把它标记 deprecated 即可,本地兜底退出要留到全链路验证后。 - DON'T 修改 R2 的地址/设计清单接口签名,R3 只消费。 ## 5. 补充内容 ### 验收标准 - 指定一个已登录用户,从设计清单进入结算页,选地址后创建订单;服务端返回的 `totalAmount` 与商品单价×数量一致。 - 双击提交只生成一单,订单号唯一。 - 订单列表/详情展示服务端数据,越权用户拿不到他人订单。 - 支付按钮在密钥未配置时返回 `configured:false` 并给出明确文案,不调用微信、不写成功记录。 - 后端 `npm run build`、前端 `npm run build:weapp` 通过。 ### 合并与依赖 - 分支名:前端与后端均为 `feat/r3-order-pay`。 - 依赖 R1 的商品表和 R2 的地址/设计清单接口,合并顺序放在 R1、R2 之后。 - 支付实现作为一个独立后续里程碑,不阻塞本分支交付。 ### 风险 - 微信小程序对订单支付有场景与类目要求,目前未备案时不要尝试真支付,容易触发审核风险。 - 物流信息当前为假数据,SHIPPED/COMPLETED 的物流时间轴要标注占位。 - 若 R2 未按约定提供地址接口,checkout 集成会被卡住;R3 开发时先按 `api-contract-v1.md` 编写,联调阶段再对齐实现。 ### 测试要求 - 后端:订单创建事务、金额重算、重复提交幂等、越权查询、状态流转、支付未配置占位响应。 - 前端:结算页创建订单、双击防抖、订单空态、状态 Tab 筛选、越权场景提示、支付未配置提示。