|
|
@@ -1,9 +1,9 @@
|
|
1
|
1
|
# 确认订单页(单商品)— 技术方案(C 端)
|
|
2
|
2
|
|
|
3
|
3
|
> **依据:** 《确认订单页(单商品)功能需求.md》v1.0
|
|
4
|
|
-> **关联:** 《确认订单页(多商品)技术方案》v1.1(**共用 checkout/order 模块**)、平台《订单管理技术方案》v1.0.2、《商品管理功能需求》v1.3.3、《店铺管理技术方案》v1.3.6、《会员管理技术方案》v1.3、《关联需求分析.md》v1.6 §11;C 端《商品详情内页技术方案》v1.4、《我的服务技术方案》v1.1;商家《运费模版技术方案》v1.1、《店铺商品列表技术方案》v1.6
|
|
5
|
|
-> **范围:** C 端 **`source=BUY_NOW`** 确认页 preview/submit + **`/api/order/**`** 支付/关单;写入 **`biz_order` / `biz_order_item`**(**单行**);**不含** 购物车路径、平台履约、微信 SDK 生产对接。
|
|
6
|
|
-> **原则:** **复用** 多商品 checkout 同一套 Service/表;**支付成功** 扣库存;**待支付不扣**;v1 **统一规格**;**不删购物车**(不经 cart)。
|
|
|
4
|
+> **关联:** 《确认订单页(多商品)技术方案》v1.3(**共用 checkout/order/pay 模块**)、《微信支付入驻与对接技术方案》v1.1、《支付流程.md》v1.3;C 端《商品详情内页技术方案》v1.4
|
|
|
5
|
+> **范围:** C 端 **`source=BUY_NOW`** 确认页 preview/submit + **`/api/order/**`** 微信支付/关单;写入 **`biz_order` / `biz_order_item`**(**单行**);**不含** 购物车路径、C 端 H5 真实调起支付。
|
|
|
6
|
+> **原则:** **复用** 多商品 checkout/order/pay;**支付成功** 扣库存;**待支付不扣**;**不删购物车**(`cart_item_id=NULL`)。
|
|
7
|
7
|
|
|
8
|
8
|
---
|
|
9
|
9
|
|
|
|
@@ -17,56 +17,46 @@
|
|
17
|
17
|
| 鉴权 | 须 **会员 Token**(`MemberWebConfig` → `/api/checkout/**`、`/api/order/**`) |
|
|
18
|
18
|
| 订单号 | `O` + `yyyyMMdd` + 6 位序号(与平台订单方案一致) |
|
|
19
|
19
|
| 支付超时 | `sys_config`:`order.pay.timeout.minutes`,默认 **1440** |
|
|
20
|
|
-| 支付 v1 | **微信支付 Mock**;生产对接 **另册** |
|
|
|
20
|
+| 支付 v1.2 | **微信 JSAPI**;`wechat.pay.mock=true` 开发直付;生产见《微信支付入驻与对接技术方案》 |
|
|
|
21
|
+| 结算 | **平台代收**(本期不涉及商户入驻路由) |
|
|
21
|
22
|
| 定时关单 | **`OrderPayTimeoutJob`**(O8) |
|
|
22
|
23
|
|
|
23
|
24
|
### 1.1 与多商品方案的关系
|
|
24
|
25
|
|
|
25
|
26
|
| 维度 | 多商品专册 | **本册(单商品)** |
|
|
26
|
27
|
|------|------------|-------------------|
|
|
27
|
|
-| 后端模块 | `com.ruoyi.web.modules.order` checkout + order | **同一套**,不另建包 |
|
|
|
28
|
+| 后端模块 | `order` checkout + order + **`pay`** | **同一套**,不另建包 |
|
|
28
|
29
|
| `source` | `CART` / `BUY_NOW` | **固定 `BUY_NOW`** |
|
|
29
|
30
|
| 行数 | ≥1 | **强制 1 行** |
|
|
30
|
31
|
| 购物车 | prepare + pay 后删行 | **不涉及** |
|
|
31
|
32
|
| 运费 | 多行聚合 | **单行简化**(§2.4) |
|
|
32
|
33
|
| 前端页 | 可共用 UI | **独立路由** + 详情跳转(见前端技术方案) |
|
|
33
|
34
|
|
|
34
|
|
-> **实现策略:** 在 `CheckoutAppServiceImpl` 内对 `source=BUY_NOW` 增加 **`items.size()==1`** 校验;其余 preview/submit/pay 逻辑 **与多商品共用**。
|
|
|
35
|
+> **实现策略:** `CheckoutAppServiceImpl` 对 `source=BUY_NOW` 校验 **`items.size()==1`**;preview/submit/pay **与多商品共用**;pay 落单走 **`OrderPaySuccessServiceImpl.completeWeChatPay`**。
|
|
35
|
36
|
|
|
36
|
|
-### 1.2 模块落位(共用 · 待建部分)
|
|
|
37
|
+### 1.2 模块落位(已实现)
|
|
37
|
38
|
|
|
38
|
39
|
```text
|
|
39
|
|
-baqing-shop/src/main/java/com/ruoyi/web/modules/order/
|
|
40
|
|
-├── controller/
|
|
41
|
|
-│ ├── CheckoutAppController.java # /api/checkout/** preview/submit
|
|
42
|
|
-│ └── OrderAppController.java # /api/order/** pay/cancel/detail
|
|
43
|
|
-├── service/
|
|
44
|
|
-│ ├── ICheckoutAppService.java
|
|
45
|
|
-│ ├── impl/CheckoutAppServiceImpl.java # BUY_NOW 单行分支
|
|
46
|
|
-│ ├── IOrderAppService.java
|
|
47
|
|
-│ └── impl/OrderAppServiceImpl.java
|
|
48
|
|
-├── support/
|
|
49
|
|
-│ ├── OrderSupport.java # 已有
|
|
50
|
|
-│ └── CheckoutSupport.java # 地址/四条件/单行运费
|
|
51
|
|
-├── constant/CheckoutConstants.java
|
|
52
|
|
-├── dto/CheckoutPreviewDTO.java、CheckoutSubmitDTO.java …
|
|
53
|
|
-└── vo/CheckoutPreviewVO.java、CheckoutSubmitResultVO.java …
|
|
54
|
|
-
|
|
55
|
|
-account/(已有)
|
|
56
|
|
-├── GET /api/member/address/list
|
|
57
|
|
-└── BizMemberAddressMapper
|
|
58
|
|
-
|
|
59
|
|
-goods/(已有)
|
|
60
|
|
-├── IGoodsPurchaseFacade.canPurchase
|
|
61
|
|
-├── BizGoodsMapper、BizGoodsFreightMapper
|
|
62
|
|
-└── BizGoodsServiceSnapshotMapper
|
|
63
|
|
-
|
|
64
|
|
-freight/(已实现)
|
|
65
|
|
-├── IFreightTemplateFacade.calcFreight / buildFreightDesc
|
|
66
|
|
-└── FreightCalcSupport
|
|
67
|
|
-
|
|
68
|
|
-home/(已有 · 上游)
|
|
69
|
|
-└── GET /api/goods/{goodsId}/can-purchase # 详情立即购买前置
|
|
|
40
|
+baqing-shop/src/main/java/com/ruoyi/web/modules/
|
|
|
41
|
+├── order/
|
|
|
42
|
+│ ├── controller/
|
|
|
43
|
+│ │ ├── CheckoutAppController.java # /api/checkout/** preview/submit
|
|
|
44
|
+│ │ └── OrderAppController.java # /api/order/** pay/cancel/detail
|
|
|
45
|
+│ ├── service/
|
|
|
46
|
+│ │ ├── ICheckoutAppService / CheckoutAppServiceImpl # BUY_NOW 单行分支
|
|
|
47
|
+│ │ ├── IOrderAppService / OrderAppServiceImpl
|
|
|
48
|
+│ │ └── IOrderPaySuccessService / OrderPaySuccessServiceImpl
|
|
|
49
|
+│ ├── support/CheckoutSupport.java
|
|
|
50
|
+│ └── constant/CheckoutConstants.java # SOURCE_BUY_NOW 等
|
|
|
51
|
+└── pay/
|
|
|
52
|
+ ├── service/IWeChatPayService / WeChatPayServiceImpl
|
|
|
53
|
+ ├── controller/WeChatPayNotifyController # POST /api/pay/wechat/notify
|
|
|
54
|
+ ├── support/WeChatPaySupport # JSAPI V3 + 回调验签
|
|
|
55
|
+ └── domain/BizPayRecord # biz_pay_record
|
|
|
56
|
+
|
|
|
57
|
+account/
|
|
|
58
|
+├── POST /api/member/wx/bind # 生产绑 openid(Mock 可跳过)
|
|
|
59
|
+└── biz_member.wx_openid
|
|
70
|
60
|
```
|
|
71
|
61
|
|
|
72
|
62
|
### 1.3 协作链(立即购买 · 本册主路径)
|
|
|
@@ -79,7 +69,8 @@ home/(已有 · 上游)
|
|
79
|
69
|
→ POST /api/checkout/submit { source: "BUY_NOW", addressId, items: [单行+remark] }
|
|
80
|
70
|
→ INSERT biz_order(0) + biz_order_item ×1(cart_item_id=NULL)
|
|
81
|
71
|
→ POST /api/order/{orderId}/pay
|
|
82
|
|
- → deductStock ×1 → status=1
|
|
|
72
|
+ → [Mock] ensurePayRecord + completeWeChatPay(扣库存、status=1)
|
|
|
73
|
+ → [生产] JSAPI 下单 → payParams → 微信回调 → completeWeChatPay
|
|
83
|
74
|
→ 或 POST /api/order/{orderId}/pay/cancel → status=4, close_type=2
|
|
84
|
75
|
|
|
85
|
76
|
biz_member_address ──► consignee_* 快照
|
|
|
@@ -124,6 +115,8 @@ WHERE goods_id = #{goodsId} AND stock >= #{qty} AND del_flag = '0'
|
|
124
|
115
|
| `biz_order_item` | **1 条** 明细快照 | `sql/biz_order.sql` + `sql/biz_order_item_checkout.sql` |
|
|
125
|
116
|
| `biz_member_address` | 读地址;快照写主表 | `sql/biz_member.sql` |
|
|
126
|
117
|
| `biz_goods` / `biz_goods_freight` | 价/库/运费 | 已有 |
|
|
|
118
|
+| `biz_pay_record` | 支付流水;`out_trade_no=order_no` | [`sql/biz_pay_record.sql`](../../../sql/biz_pay_record.sql) |
|
|
|
119
|
+| `biz_member.wx_openid` | 生产 JSAPI payer.openid | [`sql/biz_member.sql`](../../../sql/biz_member.sql) |
|
|
127
|
120
|
| `biz_goods_service_snapshot` | 预览 `serviceDesc` | 已有 |
|
|
128
|
121
|
|
|
129
|
122
|
**BUY_NOW 特征字段:**
|
|
|
@@ -277,19 +270,37 @@ CheckoutSupport.calcFreightSingle(goodsId, quantity, provinceId, goodsAmount)
|
|
277
|
270
|
|
|
278
|
271
|
---
|
|
279
|
272
|
|
|
280
|
|
-## 4. C 端接口 · `/api/order`(支付 · 取消支付前)
|
|
|
273
|
+## 4. C 端接口 · `/api/order`(微信支付 · 取消支付前)
|
|
281
|
274
|
|
|
282
|
|
-与多商品 **共用**;BUY_NOW 差异:**pay 成功后不调用 `ICartFacade`**。
|
|
|
275
|
+与多商品 **共用** `OrderAppController` / `IWeChatPayService`;BUY_NOW 差异:**`cart_item_id=NULL`,pay 成功后不删购物车行**。
|
|
283
|
276
|
|
|
284
|
|
-### 4.1 支付 `POST /api/order/{orderId}/pay`
|
|
|
277
|
+### 4.1 接口一览
|
|
|
278
|
+
|
|
|
279
|
+| 方法 | 路径 | 说明 |
|
|
|
280
|
+|------|------|------|
|
|
|
281
|
+| POST | `/api/order/{orderId}/pay` | 发起支付 → `WeChatPayParamsVO` |
|
|
|
282
|
+| POST | `/api/order/{orderId}/pay/cancel` | 用户取消 → 已关闭 |
|
|
|
283
|
+| GET | `/api/order/{orderId}` | 支付结果页 / 详情 |
|
|
|
284
|
+| POST | `/api/member/wx/bind` | 生产绑 openid(Mock **不要求**) |
|
|
|
285
|
+| POST | `/api/pay/wechat/notify` | 微信回调(Anonymous) |
|
|
|
286
|
+
|
|
|
287
|
+### 4.2 支付 `POST /api/order/{orderId}/pay`
|
|
285
|
288
|
|
|
286
|
289
|
| 项 | 说明 |
|
|
287
|
290
|
|----|------|
|
|
288
|
|
-| 前置 | `order_status=0` 且未超时 |
|
|
289
|
|
-| 事务 | **单行** `deductStock` → `order_status=1, pay_status=1, pay_time, pay_type=1` |
|
|
290
|
|
-| 购物车 | **跳过** removeItems |
|
|
|
291
|
+| 归属 | `member_id` 校验 |
|
|
|
292
|
+| 前置 | `order_status=0` 且 `now < pay_expire_time` |
|
|
|
293
|
+| Mock | `wechat.pay.mock=true` → 写 `biz_pay_record` + **同步** `completeWeChatPay`;返回 `{ mock: true }`;**无需 openid** |
|
|
|
294
|
+| 生产 | 须 `wx_openid` → JSAPI 统一下单 → 返回 **payParams**;**回调**落单 |
|
|
|
295
|
+| 落单 | `OrderPaySuccessServiceImpl.completeWeChatPay`:单行 `deductStock` → `order_status=1` |
|
|
|
296
|
+| 购物车 | `cart_item_id` 均为 null → **不调用** `ICartFacade.removeItems` |
|
|
|
297
|
+| 库存失败 | 整单失败;订单 **保持待支付** |
|
|
|
298
|
+
|
|
|
299
|
+**响应 `WeChatPayParamsVO`:** `orderId`、`orderNo`、`mock`、`payParams`(生产:`appId/timeStamp/nonceStr/package/signType/paySign`)。
|
|
291
|
300
|
|
|
292
|
|
-### 4.2 取消支付 `POST /api/order/{orderId}/pay/cancel`
|
|
|
301
|
+> 详见《微信支付入驻与对接技术方案》v1.1、《支付流程.md》§8。
|
|
|
302
|
+
|
|
|
303
|
+### 4.3 取消支付 `POST /api/order/{orderId}/pay/cancel`
|
|
293
|
304
|
|
|
294
|
305
|
| 项 | 说明 |
|
|
295
|
306
|
|----|------|
|
|
|
@@ -297,7 +308,7 @@ CheckoutSupport.calcFreightSingle(goodsId, quantity, provinceId, goodsAmount)
|
|
297
|
308
|
| 动作 | `order_status=4, close_type=2` |
|
|
298
|
309
|
| 库存 | 不扣,无需回滚 |
|
|
299
|
310
|
|
|
300
|
|
-### 4.3 订单详情 `GET /api/order/{orderId}`
|
|
|
311
|
+### 4.4 订单详情 `GET /api/order/{orderId}`
|
|
301
|
312
|
|
|
302
|
313
|
支付结果页展示:`orderNo`、`payAmount`、`items[0]`、`payExpireTime`(待支付时剩余秒数)。
|
|
303
|
314
|
|
|
|
@@ -317,14 +328,19 @@ CheckoutAppController
|
|
317
|
328
|
→ BizOrderMapper.insert + BizOrderItemMapper.insert
|
|
318
|
329
|
|
|
319
|
330
|
OrderAppController
|
|
320
|
|
- → IOrderAppService.pay / cancelPay
|
|
321
|
|
- → deductStock (1 row)
|
|
322
|
|
- → 不调用 ICartFacade
|
|
|
331
|
+ → IOrderAppService.createPay / cancelPay
|
|
|
332
|
+ → IWeChatPayService.createJsapiPay
|
|
|
333
|
+ → [Mock] OrderPaySuccessServiceImpl.completeWeChatPay
|
|
|
334
|
+WeChatPayNotifyController
|
|
|
335
|
+ → IWeChatPayService.handleNotify → completeWeChatPay
|
|
323
|
336
|
```
|
|
324
|
337
|
|
|
325
|
338
|
| 要点 | 说明 |
|
|
326
|
339
|
|------|------|
|
|
327
|
|
-| specText | BUY_NOW:入参优先;空则读 `biz_goods_attr`(attr_type=2)拼接,仍无则「默认」 |
|
|
|
340
|
+| 单行扣库存 | `completeWeChatPay` 遍历 1 条 `biz_order_item` |
|
|
|
341
|
+| 不删购物车 | BUY_NOW 明细 `cart_item_id=NULL`,`removeItems` 列表为空 |
|
|
|
342
|
+| Mock 流水 | `WeChatPayServiceImpl` Mock 分支 **先** `ensurePayRecord` 再返回 |
|
|
|
343
|
+| specText | BUY_NOW:入参优先;空则读 `biz_goods_attr` 拼接 |
|
|
328
|
344
|
| 服务快照 | `biz_goods_service_snapshot` → `service_desc` |
|
|
329
|
345
|
| 地址快照 | `consignee_address = regionName + " " + detailAddress` |
|
|
330
|
346
|
| 防重复提交 | submit 中 **禁用连点**(前端)+ 可选服务端 member 级短锁 |
|
|
|
@@ -370,11 +386,12 @@ public static final BigDecimal DEFAULT_KG_PER_PIECE = BigDecimal.ONE;
|
|
370
|
386
|
| COS-R1 备注 | `buyer_remark` |
|
|
371
|
387
|
| COS-S2 提交不扣库存 | submit 无 stock UPDATE |
|
|
372
|
388
|
| COS-S3 不经购物车 | 无 cart 协作 |
|
|
373
|
|
-| COS10 支付扣库存 | pay → deductStock |
|
|
|
389
|
+| COS10 支付扣库存 | `completeWeChatPay` → deductStock |
|
|
374
|
390
|
| COS11 取消关闭 | pay/cancel → close_type=2 |
|
|
375
|
391
|
| COS12 超时 | `pay_expire_time` + Job |
|
|
376
|
392
|
| COS13 四条件 | preview + submit |
|
|
377
|
393
|
| GD12 立即购买 | `source=BUY_NOW` |
|
|
|
394
|
+| COS8 微信支付 | `IWeChatPayService` + Mock/生产双模式 |
|
|
378
|
395
|
|
|
379
|
396
|
---
|
|
380
|
397
|
|
|
|
@@ -393,16 +410,27 @@ public static final BigDecimal DEFAULT_KG_PER_PIECE = BigDecimal.ONE;
|
|
393
|
410
|
| T9 | 支付后库存不足并发 | pay 失败;订单仍待支付 |
|
|
394
|
411
|
| T10 | 停业/下架 submit | 400;无订单 |
|
|
395
|
412
|
|
|
|
413
|
+**自动化测试(已实现):**
|
|
|
414
|
+
|
|
|
415
|
+| 类 | 覆盖 |
|
|
|
416
|
+|----|------|
|
|
|
417
|
+| `CheckoutBuyNowAppServiceImplTest` | preview/submit BUY_NOW 校验 |
|
|
|
418
|
+| `BuyNowPayFlowTest` | Mock pay 闭环;无 openid;不删购物车 |
|
|
|
419
|
+| `OrderAppServiceImplTest` | createPay BUY_NOW 跳过 cart |
|
|
|
420
|
+| `WeChatPayServiceImplTest` | Mock/生产 openid、pay_record |
|
|
|
421
|
+| `OrderPaySuccessServiceImplTest` | completeWeChatPay 幂等/扣库存 |
|
|
|
422
|
+
|
|
396
|
423
|
---
|
|
397
|
424
|
|
|
398
|
425
|
## 10. 实现分期
|
|
399
|
426
|
|
|
400
|
427
|
| 阶段 | 内容 | 状态 |
|
|
401
|
428
|
|------|------|------|
|
|
402
|
|
-| **v1.0** | checkout BUY_NOW preview/submit;order pay/cancel;`biz_order_item` 扩展字段 | **待建** |
|
|
403
|
|
-| **v1.0·运费** | `calcFreightSingle` + `biz_goods_freight` + Facade | **待建**(Facade **已有**) |
|
|
404
|
|
-| **v1.1** | 详情 `doBuyNow` 联调;支付成功页 | **待建**(前端) |
|
|
405
|
|
-| **v1.2** | 微信生产 SDK | 另册 |
|
|
|
429
|
+| **v1.0** | BUY_NOW preview/submit;order pay/cancel | **已实现** |
|
|
|
430
|
+| **v1.0·运费** | `calcFreight` + `biz_goods_freight` | **已实现** |
|
|
|
431
|
+| **v1.2·支付** | 微信 JSAPI + `biz_pay_record` + Mock/生产 + 回调 | **已实现** |
|
|
|
432
|
+| **v1.1** | 详情联调;支付成功页 | **前端已实现**(Mock pay) |
|
|
|
433
|
+| **v1.3** | H5 公众号 OAuth + 真实调起支付 | **待联调** |
|
|
406
|
434
|
|
|
407
|
435
|
---
|
|
408
|
436
|
|
|
|
@@ -411,7 +439,9 @@ public static final BigDecimal DEFAULT_KG_PER_PIECE = BigDecimal.ONE;
|
|
411
|
439
|
| 项 | 说明 |
|
|
412
|
440
|
|----|------|
|
|
413
|
441
|
| 购物车路径 | 《确认订单页(多商品)技术方案》 |
|
|
|
442
|
+| 商户微信支付入驻 | 本期平台代收;见《支付流程.md》§0 |
|
|
414
|
443
|
| 我的订单续付列表 | 另册 |
|
|
|
444
|
+| H5 外链 MWEB 支付 | 未实现 |
|
|
415
|
445
|
| 优惠券/满减 | 需求未要求 |
|
|
416
|
446
|
| 确认页 Redis 会话 | 可选;本期 **无** |
|
|
417
|
447
|
|
|
|
@@ -421,8 +451,9 @@ public static final BigDecimal DEFAULT_KG_PER_PIECE = BigDecimal.ONE;
|
|
421
|
451
|
|
|
422
|
452
|
| 版本 | 说明 |
|
|
423
|
453
|
|------|------|
|
|
424
|
|
-| **v1.0** | 首版:BUY_NOW 单行 preview/submit/pay/cancel;共用 order 模块;单行运费;与功能需求 v1.0 对齐 |
|
|
|
454
|
+| **v1.2** | 接入微信支付:共用 pay 模块、Mock 写 pay_record、BuyNowPayFlowTest;对齐多商品 v1.2 |
|
|
|
455
|
+| **v1.0** | 首版:BUY_NOW preview/submit/pay/cancel;单行运费 |
|
|
425
|
456
|
|
|
426
|
457
|
---
|
|
427
|
458
|
|
|
428
|
|
-*文档版本:v1.0 · 关联《确认订单页(单商品)功能需求.md》v1.0、《确认订单页(多商品)技术方案.md》v1.1、《订单管理技术方案.md》v1.0.2、《商品详情内页技术方案.md》v1.4 · DDL:[`sql/biz_order.sql`](../../../sql/biz_order.sql)、[`sql/biz_order_item_checkout.sql`](../../../sql/biz_order_item_checkout.sql)、[`sql/biz_goods_freight.sql`](../../../sql/biz_goods_freight.sql)*
|
|
|
459
|
+*文档版本:v1.2 · 关联《确认订单页(单商品)功能需求.md》v1.0、《确认订单页(多商品)技术方案.md》v1.3、《微信支付入驻与对接技术方案.md》v1.1 · DDL:[`sql/biz_order.sql`](../../../sql/biz_order.sql)、[`sql/biz_pay_record.sql`](../../../sql/biz_pay_record.sql)、[`sql/biz_member.sql`](../../../sql/biz_member.sql)*
|