西格玛许 2 дней назад
Родитель
Сommit
76f78082b1

+ 1 - 1
ruoyi-admin/src/main/resources/application-dev.yml

@@ -205,7 +205,7 @@ wxpay:
   certSerialNo: 79AD857EFB072D51FD47D9F6A3AE70F1FE22C9BA
   certSerialNo: 79AD857EFB072D51FD47D9F6A3AE70F1FE22C9BA
   privateKeyPath: classpath:/cert/apiclient_key.pem
   privateKeyPath: classpath:/cert/apiclient_key.pem
   privateCertPath: classpath:/cert/apiclient_cert.pem
   privateCertPath: classpath:/cert/apiclient_cert.pem
-  notifyUrl: https://dfd52ce.r31.cpolar.top/order/wx/order/notify
+  notifyUrl: http://yp1.yingpaipay.com:9053/main/order-card/wx-pay-notify
 
 
 
 
 
 

+ 16 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsOrderCardController.java

@@ -78,16 +78,31 @@ public class CsOrderCardController extends BaseController {
     }
     }
 
 
 
 
-    @Log(title = "创建支付订单", businessType = BusinessType.INSERT)
     @PostMapping("/{orderCardId}/create-order")
     @PostMapping("/{orderCardId}/create-order")
     public R<Map<String, Object>> createPayOrder(@PathVariable Long orderCardId, @RequestParam Long userId) {
     public R<Map<String, Object>> createPayOrder(@PathVariable Long orderCardId, @RequestParam Long userId) {
         return R.ok(orderCardService.createPayOrder(orderCardId, userId));
         return R.ok(orderCardService.createPayOrder(orderCardId, userId));
     }
     }
 
 
+    /**
+     * 测评咨询时自动创建结算单
+     */
+    @PostMapping("/auto-create")
+    public R<CsOrderCardVo> autoCreateForAssessment(
+            @RequestParam Long sessionId,
+            @RequestParam Long studentId,
+            @RequestParam String orderName,
+            @RequestParam java.math.BigDecimal orderPrice,
+            @RequestParam(required = false) String orderType) {
+        return R.ok(orderCardService.autoCreateForAssessment(sessionId, studentId, orderName, orderPrice, orderType));
+    }
+
 
 
     @PostMapping("/wx-pay-notify")
     @PostMapping("/wx-pay-notify")
     @SaIgnore
     @SaIgnore
     public void wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
     public void wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
+        log.info("========== 微信支付回调入口已触发 ==========");
+        log.info("请求URL: {}", request.getRequestURL().toString());
+        log.info("请求Method: {}", request.getMethod());
         try {
         try {
             // 微信支付回调验签和解密(技术实现照搬photo)
             // 微信支付回调验签和解密(技术实现照搬photo)
             String timestamp = request.getHeader("Wechatpay-Timestamp");
             String timestamp = request.getHeader("Wechatpay-Timestamp");

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/CsOrderCard.java

@@ -45,6 +45,11 @@ public class CsOrderCard {
      */
      */
     private Long originalOrderId;
     private Long originalOrderId;
 
 
+    /**
+     * 租户ID(从会话关联的岗位获取)
+     */
+    private String tenantId;
+
     /**
     /**
      * 状态: pending=待支付, paid=已支付, cancelled=已取消, expired=已失效
      * 状态: pending=待支付, paid=已支付, cancelled=已取消, expired=已失效
      */
      */

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/CsOrderCardBo.java

@@ -45,4 +45,9 @@ public class CsOrderCardBo {
      * 订单类型说明
      * 订单类型说明
      */
      */
     private String orderType;
     private String orderType;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
 }
 }

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/CsOrderCardVo.java

@@ -52,6 +52,11 @@ public class CsOrderCardVo implements Serializable {
      */
      */
     private String status;
     private String status;
 
 
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
     /**
     /**
      * 过期时间
      * 过期时间
      */
      */

+ 9 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/ICsOrderCardService.java

@@ -37,5 +37,14 @@ public interface ICsOrderCardService {
 
 
     void handlePaySuccess(String orderNo);
     void handlePaySuccess(String orderNo);
 
 
+    /**
+     * 测评咨询时自动创建结算单(不依赖客服手动发送)
+     * @param sessionId 会话ID
+     * @param studentId 学员ID
+     * @param orderName 项目名称
+     * @param orderPrice 金额
+     * @param orderType 订单类型说明
+     */
+    CsOrderCardVo autoCreateForAssessment(Long sessionId, Long studentId, String orderName, java.math.BigDecimal orderPrice, String orderType);
 
 
 }
 }

+ 162 - 7
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsOrderCardServiceImpl.java

@@ -15,7 +15,10 @@ import org.dromara.main.domain.bo.CsOrderCardBo;
 import org.dromara.main.domain.vo.CsOrderCardVo;
 import org.dromara.main.domain.vo.CsOrderCardVo;
 import org.dromara.main.mapper.CsMessageMapper;
 import org.dromara.main.mapper.CsMessageMapper;
 import org.dromara.main.mapper.CsOrderCardMapper;
 import org.dromara.main.mapper.CsOrderCardMapper;
+import org.dromara.main.mapper.CsSessionMapper;
+import org.dromara.main.mapper.MainExamEvaluationMapper;
 import org.dromara.main.mapper.MainOrderMapper;
 import org.dromara.main.mapper.MainOrderMapper;
+import org.dromara.main.mapper.MainPositionMapper;
 import org.dromara.main.mapper.MainStudentMapper;
 import org.dromara.main.mapper.MainStudentMapper;
 import org.dromara.main.service.ICsOrderCardService;
 import org.dromara.main.service.ICsOrderCardService;
 import org.dromara.main.service.ICsSessionService;
 import org.dromara.main.service.ICsSessionService;
@@ -39,6 +42,9 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
     private final Converter converter;
     private final Converter converter;
     private final MainOrderMapper orderMapper;
     private final MainOrderMapper orderMapper;
     private final MainStudentMapper studentMapper;
     private final MainStudentMapper studentMapper;
+    private final CsSessionMapper sessionMapper;
+    private final MainExamEvaluationMapper examEvaluationMapper;
+    private final MainPositionMapper mainPositionMapper;
     @Autowired
     @Autowired
     private WxPayConfig wxPayConfig;
     private WxPayConfig wxPayConfig;
 
 
@@ -62,6 +68,7 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
         orderCard.setOrderName(bo.getOrderName());
         orderCard.setOrderName(bo.getOrderName());
         orderCard.setOrderPrice(bo.getOrderPrice());
         orderCard.setOrderPrice(bo.getOrderPrice());
         orderCard.setOrderType(bo.getOrderType());
         orderCard.setOrderType(bo.getOrderType());
+        orderCard.setTenantId(bo.getTenantId());
         orderCard.setStatus("pending");
         orderCard.setStatus("pending");
         orderCard.setExpireTime(LocalDateTime.now().plusSeconds(60));
         orderCard.setExpireTime(LocalDateTime.now().plusSeconds(60));
         orderCard.setCreateTime(LocalDateTime.now());
         orderCard.setCreateTime(LocalDateTime.now());
@@ -181,13 +188,20 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
         mainOrder.setPayStatus(0);
         mainOrder.setPayStatus(0);
         mainOrder.setBusinessId(orderCard.getId());
         mainOrder.setBusinessId(orderCard.getId());
         mainOrder.setProductId(orderCard.getId());
         mainOrder.setProductId(orderCard.getId());
-        mainOrder.setTenantId(student.getTenantId());
+        // 优先使用结算单上的tenantId,其次用学员的
+        String tenantId = StringUtils.isNotBlank(orderCard.getTenantId()) ? orderCard.getTenantId() : student.getTenantId();
+        mainOrder.setTenantId(tenantId);
         mainOrder.setRemark(orderCard.getOrderName());
         mainOrder.setRemark(orderCard.getOrderName());
         log.info("创建订单 - 结算单ID: {}, 结算单价格: {}, 转换后的订单价格: {}", orderCard.getId(), orderCard.getOrderPrice(), mainOrder.getTotalAmount());
         log.info("创建订单 - 结算单ID: {}, 结算单价格: {}, 转换后的订单价格: {}", orderCard.getId(), orderCard.getOrderPrice(), mainOrder.getTotalAmount());
         orderMapper.insert(mainOrder);
         orderMapper.insert(mainOrder);
 
 
+        // 回写主订单ID到结算单
+        orderCard.setOriginalOrderId(mainOrder.getId());
+        baseMapper.updateById(orderCard);
+
         try {
         try {
-            log.info("生成微信支付参数 - OpenID: {}, 传入价格: {}, 订单号: {}", student.getOpenid(), orderCard.getOrderPrice(), mainOrder.getOrderNo());
+            log.info("生成微信支付参数 - OpenID: {}, 传入价格: {}, 订单号: {}, notifyUrl: {}",
+                student.getOpenid(), orderCard.getOrderPrice(), mainOrder.getOrderNo(), wxPayConfig.getNotifyUrl());
             Map<String, String> wxPayParams = wxPayConfig.createJsapiPayParams(
             Map<String, String> wxPayParams = wxPayConfig.createJsapiPayParams(
                 student.getOpenid(),
                 student.getOpenid(),
                 orderCard.getOrderPrice(),
                 orderCard.getOrderPrice(),
@@ -222,7 +236,7 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
     @Override
     @Override
     @Transactional(rollbackFor = Exception.class)
     @Transactional(rollbackFor = Exception.class)
     public void handlePaySuccess(String orderNo) {
     public void handlePaySuccess(String orderNo) {
-        log.info("处理支付成功,订单号:{}", orderNo);
+        log.info("========== 开始处理支付成功,订单号:{} ==========", orderNo);
 
 
         // 查找主订单
         // 查找主订单
         MainOrder mainOrder = orderMapper.selectOne(
         MainOrder mainOrder = orderMapper.selectOne(
@@ -233,24 +247,45 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
             return;
             return;
         }
         }
 
 
+        log.info("找到订单: id={}, orderNo={}, payStatus={}, orderStatus={}, businessId={}",
+            mainOrder.getId(), mainOrder.getOrderNo(), mainOrder.getPayStatus(), mainOrder.getOrderStatus(), mainOrder.getBusinessId());
+
         // 防重复处理
         // 防重复处理
-        if (mainOrder.getPayStatus() == 1) {
-            log.info("订单已处理过支付成功,订单号:{}", orderNo);
+        if (mainOrder.getPayStatus() != null && mainOrder.getPayStatus() == 1) {
+            log.info("订单已处理过支付成功,跳过,订单号:{}", orderNo);
             return;
             return;
         }
         }
 
 
-        // 更新主订单状态(按你的业务逻辑)
+        // 更新主订单状态
         mainOrder.setPayStatus(1); // 已支付
         mainOrder.setPayStatus(1); // 已支付
         mainOrder.setOrderStatus(1); // 已完成
         mainOrder.setOrderStatus(1); // 已完成
         mainOrder.setPaidAmount(mainOrder.getTotalAmount());
         mainOrder.setPaidAmount(mainOrder.getTotalAmount());
         orderMapper.updateById(mainOrder);
         orderMapper.updateById(mainOrder);
+        log.info("主订单状态已更新为已支付,订单号:{}", orderNo);
 
 
-        // 更新结算单状态(按你的业务逻辑)
+        // 更新结算单状态
         CsOrderCard orderCard = baseMapper.selectById(mainOrder.getBusinessId());
         CsOrderCard orderCard = baseMapper.selectById(mainOrder.getBusinessId());
         if (orderCard != null) {
         if (orderCard != null) {
+            log.info("找到结算单: id={}, status={}, sessionId={}", orderCard.getId(), orderCard.getStatus(), orderCard.getSessionId());
             orderCard.setStatus("paid");
             orderCard.setStatus("paid");
             orderCard.setPayTime(LocalDateTime.now());
             orderCard.setPayTime(LocalDateTime.now());
             baseMapper.updateById(orderCard);
             baseMapper.updateById(orderCard);
+            log.info("结算单状态已更新为已支付,结算单ID:{}", orderCard.getId());
+
+            // 同步更新聊天消息的payload状态
+            if (orderCard.getMsgId() != null) {
+                CsMessage msg = messageMapper.selectById(orderCard.getMsgId());
+                if (msg != null && StringUtils.isNotBlank(msg.getPayload())) {
+                    try {
+                        Map<String, Object> payload = JSONUtil.toBean(msg.getPayload(), Map.class);
+                        payload.put("status", "paid");
+                        msg.setPayload(JSONUtil.toJsonStr(payload));
+                        messageMapper.updateById(msg);
+                    } catch (Exception e) {
+                        log.warn("更新消息payload状态失败: {}", e.getMessage());
+                    }
+                }
+            }
 
 
             log.info("支付成功处理完成,订单号:{},结算单ID:{}", orderNo, orderCard.getId());
             log.info("支付成功处理完成,订单号:{},结算单ID:{}", orderNo, orderCard.getId());
         }
         }
@@ -268,5 +303,125 @@ public class CsOrderCardServiceImpl implements ICsOrderCardService {
         return amount == null ? BigDecimal.ZERO : amount;
         return amount == null ? BigDecimal.ZERO : amount;
     }
     }
 
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public CsOrderCardVo autoCreateForAssessment(Long sessionId, Long studentId, String orderName, BigDecimal orderPrice, String orderType) {
+        // 检查该会话是否已有待支付的结算单,避免重复创建
+        CsOrderCard existing = baseMapper.selectOne(Wrappers.<CsOrderCard>lambdaQuery()
+            .eq(CsOrderCard::getSessionId, sessionId)
+            .eq(CsOrderCard::getStatus, "pending")
+            .last("LIMIT 1"));
+        if (existing != null) {
+            CsOrderCardVo vo = converter.convert(existing, CsOrderCardVo.class);
+            if (vo != null && "pending".equals(vo.getStatus())) {
+                long seconds = Duration.between(LocalDateTime.now(), existing.getExpireTime()).getSeconds();
+                vo.setCountdownSeconds(Math.max(0, seconds));
+            }
+            return vo;
+        }
+
+        // 从会话获取tenantId、测评价格(结算单本质上是针对测评的)
+        String tenantId = "000000";
+        BigDecimal resolvedPrice = orderPrice; // 默认使用前端传入的价格
+        CsSession session = sessionMapper.selectById(sessionId);
+        if (session != null && StringUtils.isNotBlank(session.getSourceId())) {
+            String sourceId = session.getSourceId();
+            if (sourceId.startsWith("assessment_")) {
+                // 测评入口
+                try {
+                    Long evalId = Long.parseLong(sourceId.substring("assessment_".length()));
+                    MainExamEvaluation evaluation = examEvaluationMapper.selectById(evalId);
+                    if (evaluation != null) {
+                        if (evaluation.getPositionId() != null) {
+                            MainPosition position = mainPositionMapper.selectById(evaluation.getPositionId());
+                            if (position != null && StringUtils.isNotBlank(position.getTenantId())) {
+                                tenantId = position.getTenantId();
+                            }
+                        }
+                        // 前端没传价或传了0时,用测评自身的price
+                        if ((resolvedPrice == null || resolvedPrice.compareTo(BigDecimal.ZERO) <= 0)
+                                && evaluation.getPrice() != null && evaluation.getPrice().compareTo(BigDecimal.ZERO) > 0) {
+                            resolvedPrice = evaluation.getPrice();
+                        }
+                    }
+                } catch (Exception e) { log.warn("从测评获取信息失败: {}", e.getMessage()); }
+            } else if (sourceId.startsWith("job_")) {
+                // 岗位入口 → 从岗位关联的测评获取价格
+                try {
+                    Long posId = Long.parseLong(sourceId.substring("job_".length()));
+                    MainPosition position = mainPositionMapper.selectById(posId);
+                    if (position != null) {
+                        if (StringUtils.isNotBlank(position.getTenantId())) {
+                            tenantId = position.getTenantId();
+                        }
+                        // 查找该岗位下第一个有效测评的价格
+                        if ((resolvedPrice == null || resolvedPrice.compareTo(BigDecimal.ZERO) <= 0)) {
+                            List<MainExamEvaluation> evals = examEvaluationMapper.selectList(
+                                Wrappers.<MainExamEvaluation>lambdaQuery()
+                                    .eq(MainExamEvaluation::getPositionId, posId)
+                                    .eq(MainExamEvaluation::getStatus, "1")
+                                    .last("LIMIT 1"));
+                            if (!evals.isEmpty()) {
+                                MainExamEvaluation firstEval = evals.get(0);
+                                if (firstEval.getPrice() != null && firstEval.getPrice().compareTo(BigDecimal.ZERO) > 0) {
+                                    resolvedPrice = firstEval.getPrice();
+                                }
+                                if (StringUtils.isBlank(orderName)) {
+                                    orderName = firstEval.getEvaluationName() + "-测评服务费";
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) { log.warn("从岗位获取信息失败: {}", e.getMessage()); }
+            }
+        }
+
+        // 创建消息
+        CsMessage message = new CsMessage();
+        message.setSessionId(sessionId);
+        message.setMsgNo("ORDER_" + System.currentTimeMillis());
+        message.setSenderType(2);
+        message.setSenderId(session != null ? session.getWaiterId() : null);
+        message.setMsgType("order_card");
+        message.setStatus(1);
+        message.setIsRead(0);
+        message.setSendTime(LocalDateTime.now());
+        messageMapper.insert(message);
+
+        // 创建结算单(价格使用解析后的resolvedPrice,兜底0.01)
+        BigDecimal finalPrice = (resolvedPrice != null && resolvedPrice.compareTo(BigDecimal.ZERO) > 0) ? resolvedPrice : new BigDecimal("0.01");
+        CsOrderCard orderCard = new CsOrderCard();
+        orderCard.setMsgId(message.getId());
+        orderCard.setSessionId(sessionId);
+        orderCard.setOrderName(orderName);
+        orderCard.setOrderPrice(finalPrice);
+        orderCard.setOrderType(orderType);
+        orderCard.setTenantId(tenantId);
+        orderCard.setStatus("pending");
+        orderCard.setExpireTime(LocalDateTime.now().plusMinutes(30)); // 30分钟过期
+        orderCard.setCreateTime(LocalDateTime.now());
+        baseMapper.insert(orderCard);
+
+        // 回写payload到消息(同时写入price和amount,兼容前后端)
+        Map<String, Object> payload = new HashMap<>();
+        payload.put("orderCardId", orderCard.getId());
+        payload.put("name", orderCard.getOrderName());
+        payload.put("amount", orderCard.getOrderPrice().toString());
+        payload.put("price", orderCard.getOrderPrice().toString());
+        payload.put("status", "pending");
+        payload.put("expireTime", orderCard.getExpireTime().toString());
+        payload.put("countdownSeconds", 1800);
+        message.setPayload(JSONUtil.toJsonStr(payload));
+        messageMapper.updateById(message);
+
+        sessionService.updateLastMessage(sessionId, "[结算单]" + orderCard.getOrderName());
+
+        CsOrderCardVo vo = converter.convert(orderCard, CsOrderCardVo.class);
+        if (vo != null) {
+            vo.setCountdownSeconds(1800L);
+        }
+        return vo;
+    }
+
 
 
 }
 }

+ 14 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackCandidateServiceImpl.java

@@ -3,6 +3,7 @@ package org.dromara.main.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.OssService;
 import org.dromara.main.domain.MainBackCandidate;
 import org.dromara.main.domain.MainBackCandidate;
 import org.dromara.main.domain.MainPosition;
 import org.dromara.main.domain.MainPosition;
 import org.dromara.main.mapper.MainBackCandidateMapper;
 import org.dromara.main.mapper.MainBackCandidateMapper;
@@ -28,6 +29,7 @@ public class MainBackCandidateServiceImpl implements IMainBackCandidateService {
     private final MainBackCandidateMapper mainBackCandidateMapper;
     private final MainBackCandidateMapper mainBackCandidateMapper;
     private final MainPositionMapper mainPositionMapper;
     private final MainPositionMapper mainPositionMapper;
     private final SysTenantMapper sysTenantMapper;
     private final SysTenantMapper sysTenantMapper;
+    private final OssService ossService;
 
 
     @Override
     @Override
     public List<Map<String, Object>> getStudentOfferList(Long studentId) {
     public List<Map<String, Object>> getStudentOfferList(Long studentId) {
@@ -68,7 +70,18 @@ public class MainBackCandidateServiceImpl implements IMainBackCandidateService {
             map.put("studentId", item.getStudentId());
             map.put("studentId", item.getStudentId());
             map.put("enterpriseStatus", item.getEnterpriseStatus());
             map.put("enterpriseStatus", item.getEnterpriseStatus());
             map.put("studentStatus", item.getStudentStatus());
             map.put("studentStatus", item.getStudentStatus());
-            map.put("offerFileUrl", item.getOfferFileUrl());
+            // offerFileUrl 存的是ossId,需要转为真实URL
+            String rawOfferUrl = item.getOfferFileUrl();
+            if (rawOfferUrl != null && !rawOfferUrl.isBlank()) {
+                try {
+                    String realUrl = ossService.selectUrlByIds(rawOfferUrl);
+                    map.put("offerFileUrl", realUrl != null ? realUrl : rawOfferUrl);
+                } catch (Exception e) {
+                    map.put("offerFileUrl", rawOfferUrl);
+                }
+            } else {
+                map.put("offerFileUrl", null);
+            }
             map.put("offerFileName", item.getOfferFileName());
             map.put("offerFileName", item.getOfferFileName());
             map.put("offerTime", item.getOfferTime());
             map.put("offerTime", item.getOfferTime());
             map.put("studentReplyTime", item.getStudentReplyTime());
             map.put("studentReplyTime", item.getStudentReplyTime());