Răsfoiți Sursa

feat: enhance ProductOrderService/Controller and add LogisticsController

Add checkout, payment prep, payment notification handling, and logistics tracking.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
liaoxg 2 luni în urmă
părinte
comite
d5ff992db7

+ 49 - 3
cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductOrderController.java

@@ -1,16 +1,22 @@
 package com.etotem.cfc.controller.product;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.CreateProductOrderDTO;
 import com.etotem.cfc.dto.ProductOrderDTO;
+import com.etotem.cfc.service.PaymentService;
 import com.etotem.cfc.service.ProductOrderService;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.bind.annotation.RequestAttribute;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -21,6 +27,9 @@ public class ProductOrderController {
     @Resource
     private ProductOrderService orderService;
 
+    @Resource
+    private PaymentService paymentService;
+
     @PostMapping("/create")
     public Result<ProductOrderDTO> create(@RequestBody CreateProductOrderDTO dto,
                                          @RequestAttribute("userId") Long userId) {
@@ -28,8 +37,8 @@ public class ProductOrderController {
     }
 
     @PostMapping("/pay")
-    public Result<ProductOrderDTO> pay(@RequestBody Map<String, Object> params,
-                                      @RequestAttribute("userId") Long userId) {
+    public Result<Map<String, Object>> pay(@RequestBody Map<String, Object> params,
+                                          @RequestAttribute("userId") Long userId) {
         String orderNo = (String) params.get("orderNo");
         return orderService.pay(orderNo, userId);
     }
@@ -68,4 +77,41 @@ public class ProductOrderController {
         String orderNo = (String) params.get("orderNo");
         return orderService.confirm(orderNo, userId);
     }
+
+    @PostMapping("/notify")
+    public Map<String, Object> notify(HttpServletRequest request) {
+        try {
+            BufferedReader reader = request.getReader();
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+            String requestBody = sb.toString();
+            String signature = request.getHeader("Wechatpay-Signature");
+
+            Map<String, Object> result = paymentService.handleWechatNotify(requestBody, signature);
+
+            if ("SUCCESS".equals(result.get("code"))) {
+                JSONObject body = JSON.parseObject(requestBody);
+                JSONObject resource = body.getJSONObject("resource");
+                if (resource != null) {
+                    String ciphertext = resource.getString("ciphertext");
+                    String associatedData = resource.getString("associated_data");
+                    String nonce = resource.getString("nonce");
+                    String plaintext = paymentService.decryptAes256Gcm(ciphertext, associatedData, nonce);
+                    JSONObject payResult = JSON.parseObject(plaintext);
+                    String orderNo = payResult.getString("out_trade_no");
+                    String transactionId = payResult.getString("transaction_id");
+                    orderService.handlePaymentSuccess(orderNo, transactionId);
+                }
+            }
+            return result;
+        } catch (Exception e) {
+            Map<String, Object> err = new HashMap<>();
+            err.put("code", "FAIL");
+            err.put("message", "处理异常");
+            return err;
+        }
+    }
 }

+ 27 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/shop/LogisticsController.java

@@ -0,0 +1,27 @@
+package com.etotem.cfc.controller.shop;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.service.ProductOrderService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/shop")
+public class LogisticsController {
+
+    @Resource
+    private ProductOrderService productOrderService;
+
+    @PostMapping("/logistics/query")
+    public Result<Map<String, Object>> query(@RequestBody Map<String, Object> params,
+                                              @RequestAttribute("userId") Long userId) {
+        Long orderId = Long.parseLong(params.get("orderId").toString());
+        return productOrderService.queryLogistics(orderId, userId);
+    }
+}

+ 111 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java

@@ -3,6 +3,7 @@ package com.etotem.cfc.service;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.CreateProductOrderDTO;
+import com.etotem.cfc.dto.OrderItemVO;
 import com.etotem.cfc.dto.ProductOrderDTO;
 import com.etotem.cfc.entity.Product;
 import com.etotem.cfc.entity.ProductOrder;
@@ -14,8 +15,11 @@ import com.etotem.cfc.service.CommissionService;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Service
@@ -36,10 +40,18 @@ public class ProductOrderService {
     @Resource
     private CommissionService commissionService;
 
+    @Resource
+    private PaymentService paymentService;
+
     public Result<ProductOrderDTO> create(CreateProductOrderDTO dto, Long buyerId) {
         if (buyerId == null) {
             return Result.error("请先登录");
         }
+
+        if (dto.getItems() != null && !dto.getItems().isEmpty()) {
+            return createMultiItem(dto, buyerId);
+        }
+
         Product product = productMapper.selectById(dto.getProductId());
         if (product == null) {
             return Result.error("商品不存在");
@@ -65,13 +77,67 @@ public class ProductOrderService {
         order.setStatus("pending");
         order.setPaymentMethod(dto.getPaymentMethod() != null ? dto.getPaymentMethod() : "wechat");
         order.setRemark(dto.getRemark());
+        order.setCoverImage(product.getCoverImage());
+        order.setDiscountAmount(dto.getDiscountAmount());
+        order.setCouponId(dto.getCouponId());
+        order.setAddressSnapshot(dto.getAddressSnapshot());
+        order.setCancelAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000));
         order.setCreatedAt(new Date());
         order.setUpdatedAt(new Date());
         orderMapper.insert(order);
         return Result.success(ProductOrderDTO.from(order));
     }
 
-    public Result<ProductOrderDTO> pay(String orderNo, Long userId) {
+    private Result<ProductOrderDTO> createMultiItem(CreateProductOrderDTO dto, Long buyerId) {
+        int totalAmount = 0;
+        int totalQuantity = 0;
+        OrderItemVO firstItem = dto.getItems().get(0);
+        Product firstProduct = null;
+
+        for (OrderItemVO item : dto.getItems()) {
+            Product product = productMapper.selectById(item.getProductId());
+            if (product == null) {
+                return Result.error("商品不存在: " + item.getProductId());
+            }
+            if (!"on_shelf".equals(product.getStatus())) {
+                return Result.error("商品已下架: " + product.getName());
+            }
+            if (product.getStock() != null && product.getStock() < item.getQuantity()) {
+                return Result.error("库存不足: " + product.getName());
+            }
+            totalAmount += product.getPrice() * item.getQuantity();
+            totalQuantity += item.getQuantity();
+            if (firstProduct == null) {
+                firstProduct = product;
+            }
+        }
+
+        User buyer = userMapper.selectById(buyerId);
+        ProductOrder order = new ProductOrder();
+        order.setOrderNo(generateOrderNo());
+        order.setProductId(firstProduct.getId());
+        order.setProductName(firstProduct.getName() + "等多件");
+        order.setProductType(firstProduct.getProductType());
+        order.setBuyerId(buyerId);
+        order.setFamilyId(buyer != null ? buyer.getFamilyId() : null);
+        order.setQuantity(totalQuantity);
+        order.setUnitPrice(firstProduct.getPrice());
+        order.setTotalAmount(totalAmount);
+        order.setStatus("pending");
+        order.setPaymentMethod(dto.getPaymentMethod() != null ? dto.getPaymentMethod() : "wechat");
+        order.setRemark(dto.getRemark());
+        order.setCoverImage(firstItem.getCoverImage() != null ? firstItem.getCoverImage() : firstProduct.getCoverImage());
+        order.setDiscountAmount(dto.getDiscountAmount());
+        order.setCouponId(dto.getCouponId());
+        order.setAddressSnapshot(dto.getAddressSnapshot());
+        order.setCancelAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000));
+        order.setCreatedAt(new Date());
+        order.setUpdatedAt(new Date());
+        orderMapper.insert(order);
+        return Result.success(ProductOrderDTO.from(order));
+    }
+
+    public Result<Map<String, Object>> pay(String orderNo, Long userId) {
         ProductOrder order = orderMapper.selectOne(
             new LambdaQueryWrapper<ProductOrder>().eq(ProductOrder::getOrderNo, orderNo));
         if (order == null) {
@@ -87,13 +153,37 @@ public class ProductOrderService {
         if (!stockOk) {
             return Result.error("库存不足");
         }
+        Map<String, Object> wechatPayParams = paymentService.createWechatPrepay(
+                orderNo, order.getProductName(), order.getTotalAmount());
         order.setStatus("paid");
         order.setPaidAt(new Date());
         order.setUpdatedAt(new Date());
         orderMapper.updateById(order);
         commissionService.settle(order.getId(), "product", order.getBuyerId(),
                 order.getTotalAmount(), order.getProductId());
-        return Result.success(ProductOrderDTO.from(order));
+        Map<String, Object> result = new HashMap<>();
+        result.put("order", ProductOrderDTO.from(order));
+        result.put("wechatPayParams", wechatPayParams);
+        return Result.success(result);
+    }
+
+    public Result<String> handlePaymentSuccess(String orderNo, String transactionId) {
+        ProductOrder order = orderMapper.selectOne(
+            new LambdaQueryWrapper<ProductOrder>().eq(ProductOrder::getOrderNo, orderNo));
+        if (order == null) {
+            return Result.error("订单不存在");
+        }
+        if (!"pending".equals(order.getStatus())) {
+            return Result.success("订单状态非待支付,跳过");
+        }
+        order.setStatus("paid");
+        order.setTransactionId(transactionId);
+        order.setPaidAt(new Date());
+        order.setUpdatedAt(new Date());
+        orderMapper.updateById(order);
+        commissionService.settle(order.getId(), "product", order.getBuyerId(),
+                order.getTotalAmount(), order.getProductId());
+        return Result.success("支付成功");
     }
 
     public Result<String> cancel(String orderNo, Long userId) {
@@ -186,6 +276,25 @@ public class ProductOrderService {
         return Result.success("订单已完成");
     }
 
+    public Result<Map<String, Object>> queryLogistics(Long orderId, Long userId) {
+        ProductOrder order = orderMapper.selectById(orderId);
+        if (order == null) {
+            return Result.error("订单不存在");
+        }
+        Product product = productMapper.selectById(order.getProductId());
+        boolean isBuyer = order.getBuyerId().equals(userId);
+        boolean isVendor = product != null && product.getVendorId() != null && product.getVendorId().equals(userId);
+        if (!isBuyer && !isVendor) {
+            return Result.error("无权查看");
+        }
+        Map<String, Object> result = new HashMap<>();
+        result.put("logisticsNo", order.getLogisticsNo());
+        result.put("logisticsCompany", order.getLogisticsCompany());
+        result.put("status", order.getStatus());
+        result.put("trackingData", new ArrayList<>());
+        return Result.success(result);
+    }
+
     private String generateOrderNo() {
         return "PO" + System.currentTimeMillis();
     }