浏览代码

Merge remote-tracking branch 'origin/master'

yfh 9 小时之前
父节点
当前提交
8863201b92
共有 50 个文件被更改,包括 2100 次插入83 次删除
  1. 23 0
      fs-admin/src/main/java/com/fs/hisStore/task/expressTask.java
  2. 2 2
      fs-admin/src/main/java/com/fs/live/controller/LiveAutoTaskController.java
  3. 14 0
      fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java
  4. 2 2
      fs-company/src/main/java/com/fs/company/controller/live/LiveAutoTaskController.java
  5. 13 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveCouponController.java
  6. 14 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveOrderController.java
  7. 6 5
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  8. 6 1
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  9. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  10. 1 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodDaysService.java
  11. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  12. 36 15
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  13. 74 18
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  14. 4 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  15. 101 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  16. 0 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  17. 1 1
      fs-service/src/main/java/com/fs/live/domain/LiveAutoTask.java
  18. 8 2
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  19. 12 11
      fs-service/src/main/java/com/fs/live/param/LiveDataParam.java
  20. 1 1
      fs-service/src/main/java/com/fs/live/service/ILiveAutoTaskService.java
  21. 26 21
      fs-service/src/main/java/com/fs/live/service/impl/LiveAutoTaskServiceImpl.java
  22. 20 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  23. 1 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  24. 141 0
      fs-service/src/main/java/com/fs/pay/domain/PaymentMiniProgramConfig.java
  25. 70 0
      fs-service/src/main/java/com/fs/pay/mapper/PaymentMiniProgramConfigMapper.java
  26. 104 0
      fs-service/src/main/java/com/fs/wx/order/domain/FsWxExpressTask.java
  27. 21 0
      fs-service/src/main/java/com/fs/wx/order/dto/Contact.java
  28. 27 0
      fs-service/src/main/java/com/fs/wx/order/dto/OrderKey.java
  29. 77 0
      fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryRequest.java
  30. 236 0
      fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryResponse.java
  31. 17 0
      fs-service/src/main/java/com/fs/wx/order/dto/Payer.java
  32. 26 0
      fs-service/src/main/java/com/fs/wx/order/dto/ShippingItem.java
  33. 42 0
      fs-service/src/main/java/com/fs/wx/order/dto/UploadShippingInfoRequest.java
  34. 21 0
      fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiConfig.java
  35. 20 0
      fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiResponse.java
  36. 117 0
      fs-service/src/main/java/com/fs/wx/order/mapper/FsWxExpressTaskMapper.java
  37. 30 0
      fs-service/src/main/java/com/fs/wx/order/service/ExpressToWxHolder.java
  38. 27 0
      fs-service/src/main/java/com/fs/wx/order/service/ExpressToWxService.java
  39. 20 0
      fs-service/src/main/java/com/fs/wx/order/service/OrderQueryService.java
  40. 128 0
      fs-service/src/main/java/com/fs/wx/order/service/ShippingService.java
  41. 46 0
      fs-service/src/main/java/com/fs/wx/order/service/WeChatAuthFactory.java
  42. 22 0
      fs-service/src/main/java/com/fs/wx/order/service/WeChatAuthService.java
  43. 114 0
      fs-service/src/main/java/com/fs/wx/order/service/impl/InMemoryWeChatAuthServiceImpl.java
  44. 107 0
      fs-service/src/main/java/com/fs/wx/order/service/impl/LiveExpressToWxService.java
  45. 107 0
      fs-service/src/main/java/com/fs/wx/order/service/impl/ShopExpressToWxService.java
  46. 5 0
      fs-service/src/main/resources/application-common.yml
  47. 2 2
      fs-service/src/main/resources/application-druid-jzzx.yml
  48. 5 0
      fs-service/src/main/resources/mapper/course/FsUserCoursePeriodDaysMapper.xml
  49. 1 0
      fs-service/src/main/resources/mapper/live/LiveAutoTaskMapper.xml
  50. 195 0
      fs-service/src/main/resources/mapper/pay/PaymentMiniProgramConfigMapper.xml

+ 23 - 0
fs-admin/src/main/java/com/fs/hisStore/task/expressTask.java

@@ -0,0 +1,23 @@
+package com.fs.hisStore.task;
+
+
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 物流信息定时任务
+ */
+@Slf4j
+@Component("expressTask")
+public class expressTask {
+
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+
+    public void syncExpressToWx(){
+        fsStoreOrderScrmService.syncExpressToWx();
+    }
+
+}

+ 2 - 2
fs-admin/src/main/java/com/fs/live/controller/LiveAutoTaskController.java

@@ -116,9 +116,9 @@ public class LiveAutoTaskController extends BaseController
 //    @PreAuthorize("@ss.hasPermi('shop:task:edit')")
     @Log(title = "直播间自动化任务配置", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody LiveAutoTask liveAutoTask)
+    public R edit(@RequestBody LiveAutoTask liveAutoTask)
     {
-        return toAjax(liveAutoTaskService.updateLiveAutoTask(liveAutoTask));
+        return liveAutoTaskService.updateLiveAutoTask(liveAutoTask);
     }
 
     /**

+ 14 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java

@@ -58,6 +58,7 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
@@ -155,6 +156,11 @@ public class LiveOrderController extends BaseController
         List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
         for (LiveOrderVoZm vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
         }
         return getDataTable(list);
     }
@@ -168,6 +174,14 @@ public class LiveOrderController extends BaseController
     public AjaxResult exportZm(LiveOrder liveOrder)
     {
         List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
+        for (LiveOrderVoZm vo : list){
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
+        }
         ExcelUtil<LiveOrderVoZm> util = new ExcelUtil<LiveOrderVoZm>(LiveOrderVoZm.class);
         return util.exportExcel(list, "订单数据");
     }

+ 2 - 2
fs-company/src/main/java/com/fs/company/controller/live/LiveAutoTaskController.java

@@ -116,9 +116,9 @@ public class LiveAutoTaskController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:task:edit')")
     @Log(title = "直播间自动化任务配置", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody LiveAutoTask liveAutoTask)
+    public R edit(@RequestBody LiveAutoTask liveAutoTask)
     {
-        return toAjax(liveAutoTaskService.updateLiveAutoTask(liveAutoTask));
+        return liveAutoTaskService.updateLiveAutoTask(liveAutoTask);
     }
 
     /**

+ 13 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveCouponController.java

@@ -73,6 +73,19 @@ public class LiveCouponController extends BaseController
         return AjaxResult.success(liveCouponService.selectLiveCouponById(couponId));
     }
 
+    /**
+     * 查询优惠券列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveCoupon:list')")
+    @GetMapping("/listOn")
+    public TableDataInfo listOn(@RequestParam("liveId") Long liveId)
+    {
+        startPage();
+        List<LiveCoupon> list = liveCouponService.listOn(liveId);
+        return getDataTable(list);
+    }
+
+
     /**
      * 新增优惠券
      */

+ 14 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveOrderController.java

@@ -36,6 +36,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
 
@@ -122,6 +123,11 @@ public class LiveOrderController extends BaseController
         List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
         for (LiveOrderVoZm vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
         }
         return getDataTable(list);
     }
@@ -136,6 +142,14 @@ public class LiveOrderController extends BaseController
     {
         liveOrder.setCompanyId(SecurityUtils.getLoginUser().getUser().getCompanyId());
         List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
+        for (LiveOrderVoZm vo : list){
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
+        }
         ExcelUtil<LiveOrderVoZm> util = new ExcelUtil<LiveOrderVoZm>(LiveOrderVoZm.class);
         return util.exportExcel(list, "订单数据");
     }

+ 6 - 5
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -142,6 +142,11 @@ public class Task {
                 }
             }
         });
+        if(!liveList.isEmpty()){
+            for (Live live : liveList) {
+                liveService.updateLiveEntity(live);
+            }
+        }
         String key = "live:auto_task:";
         if (!startLiveList.isEmpty()) {
             for (Live live : startLiveList) {
@@ -182,11 +187,7 @@ public class Task {
             // 重新更新所有在直播的缓存
             liveService.asyncToCache();
         }
-        if(!liveList.isEmpty()){
-            for (Live live : liveList) {
-                liveService.updateLiveEntity(live);
-            }
-        }
+
     }
     @Scheduled(cron = "0/1 * * * * ?")
     @DistributeLock(key = "liveLotteryTask", scene = "task")

+ 6 - 1
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -25,6 +25,7 @@ import org.springframework.stereotype.Component;
 
 import javax.websocket.*;
 import javax.websocket.server.ServerEndpoint;
+import java.io.EOFException;
 import java.io.IOException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
@@ -466,7 +467,11 @@ public class WebSocketServer {
     //错误时调用
     @OnError
     public void onError(Session session, Throwable throwable) {
-        log.error("webSocKet连接错误 msg: {}", throwable.getMessage(), throwable);
+        if (throwable instanceof EOFException) {
+            log.info("WebSocket连接被客户端正常关闭(EOF),sessionId: {}", session.getId());
+        } else {
+            log.error("WebSocket连接错误", throwable);
+        }
     }
 
     /**

+ 2 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java

@@ -131,4 +131,6 @@ public interface FsUserCoursePeriodDaysMapper extends BaseMapper<FsUserCoursePer
      * @Date 2025/11/18 11:04
      */
     List<Long> selectFsUserCoursePeriodDaysForLastById(FsUserCoursePeriodDays param);
+
+    List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysByCourseId(@Param("courseId") Long courseId);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodDaysService.java

@@ -122,4 +122,5 @@ public interface IFsUserCoursePeriodDaysService extends IService<FsUserCoursePer
 
     List<Long> selectFsUserCoursePeriodDaysByTime(String periodSTime,String periodETime);
 
+        List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysByCourseId(Long courseId);
     }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -479,6 +479,11 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         return fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysByTime(periodSTime,periodETime);
     }
 
+    @Override
+    public List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysByCourseId(Long courseId) {
+        return fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysByCourseId(courseId);
+    }
+
     private static FsCourseAnalysisCountVO getCourseAnalysisCountVO(FsUserCoursePeriodDays v, Map<Long, FsCourseAnalysisCountVO> courseMap, Map<Long, FsCourseAnalysisCountVO> redPacketMap, Map<Long, FsCourseAnalysisCountVO> answerMap) {
         FsCourseAnalysisCountVO countVO = new FsCourseAnalysisCountVO();
         FsCourseAnalysisCountVO courseVO = courseMap.getOrDefault(v.getVideoId(), countVO);

+ 36 - 15
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -26,6 +26,7 @@ import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseListParam;
+import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
@@ -128,6 +129,9 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private IFsUserIntegralLogsService userIntegralLogsService;
 
+    @Autowired
+    private IFsUserCoursePeriodDaysService fsUserCoursePeriodDaysService;
+
     private static final String realLink = "/courseH5/pages/course/learning?course=";
     public static final String shortLink = "/courseH5/pages/course/learning?s=";
 
@@ -690,24 +694,41 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Transactional(rollbackFor = Exception.class) // 显式声明事务
     public int copyFsUserCourse(Long courseId) {
         FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(courseId);
-        if(fsUserCourse != null){
-            fsUserCourse.setCourseId(null);
-            fsUserCourseService.insertFsUserCourse(fsUserCourse);
-            Long newCourseId = fsUserCourse.getCourseId();
+        try {
+            if(fsUserCourse != null){
+                fsUserCourse.setCourseId(null);
+                fsUserCourseService.insertFsUserCourse(fsUserCourse);
+                Long newCourseId = fsUserCourse.getCourseId();
 
-            if (newCourseId == null) {
-                throw new RuntimeException("课程插入失败,无法获取新课程ID");
-            }
+                if (newCourseId == null) {
+                    throw new RuntimeException("课程插入失败,无法获取新课程ID");
+                }
+
+                FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
+                fsUserCourseVideo.setCourseId(courseId);
+                List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+                if(list != null && !list.isEmpty()){
+                    for (FsUserCourseVideo courseVideo : list) {
+                        courseVideo.setVideoId(null);
+                        courseVideo.setCourseId(newCourseId);
+                        fsUserCourseVideoService.insertFsUserCourseVideo(courseVideo);
+                    }
+                }
+
+                //增加手动发课部分的复制
+                List<FsUserCoursePeriodDays> fsUserCoursePeriodDays =  fsUserCoursePeriodDaysService.selectFsUserCoursePeriodDaysByCourseId(courseId);
+                if(fsUserCoursePeriodDays != null && !fsUserCoursePeriodDays.isEmpty()){
+                    for (FsUserCoursePeriodDays periodDays : fsUserCoursePeriodDays) {
+                        periodDays.setId(null);
+                        periodDays.setCourseId(newCourseId);
+                        fsUserCoursePeriodDaysService.insertFsUserCoursePeriodDays(periodDays);
+                    }
+                }
 
-            FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
-            fsUserCourseVideo.setCourseId(courseId);
-            List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
-            for (FsUserCourseVideo courseVideo : list) {
-                courseVideo.setVideoId(null);
-                courseVideo.setCourseId(newCourseId);
-                fsUserCourseVideoService.insertFsUserCourseVideo(courseVideo);
+                return 1;
             }
-            return 1;
+        } catch (RuntimeException e) {
+            throw new RuntimeException("复制失败,请联系开发人员");
         }
 
         return 0;

+ 74 - 18
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -710,13 +710,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private R handleRoom(FsUserCourseVideoAddKfUParam param,FsUser user) {
         //查询客户列表
         List<QwExternalContact> contacts = qwExternalContactMapper.selectQwExternalContactListVOByfsUserId(user.getUserId());
-        List<QwExternalContact> nonNullContacts = contacts.stream()
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
-        log.info("查出来的企微客户数量:"+nonNullContacts.size());
-        if (!nonNullContacts.isEmpty()){
+        log.info("查出来的企微客户数量:"+contacts.size());
+        if (!contacts.isEmpty()){
             //找出对应销售匹配的客户
-            QwExternalContact matchedContact = nonNullContacts.stream()
+            QwExternalContact matchedContact = contacts.stream()
                     .filter(contact -> contact.getQwUserId().equals(Long.parseLong(param.getQwUserId())))
                     .findFirst()
                     .orElse(null);
@@ -732,18 +729,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                     createWatchLog(param);
                 }
                 return R.error(567,"群聊通用链接").put("qwExternalId", matchedContact.getId());
-            }else {
-                QwExternalContact contact = nonNullContacts.get(0);
-                log.info("匹配到的第一个企微用户:"+contact.getUserId());
-                log.info("企微id:"+contact.getId());
-                log.info("用户:"+param.getVideoId());
-                log.info("企微用户:"+param.getQwUserId());
-                param.setQwExternalId(contact.getId());
-                FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(contact.getId(), param.getVideoId(),param.getQwUserId());
-                if (log==null){
-                    createWatchLog(param);
-                }
-                return R.error(567,"群聊通用链接").put("qwExternalId", contact.getId());
             }
         }
 
@@ -752,6 +737,77 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         return addCustomerService(param.getQwUserId(),msg);
 
+//        QwGroupChatDetailsResult result = qwApiService.groupChatDetails(courseLink.getChatId(), param.getCorpId());
+//        if(result.getErrCode() != 0){
+//            log.info("企微接口请求失败,请联系管理员:" +result.getErrMsg());
+//            return R.error("不是此群成员");
+//        }
+//        List<QwGroupChatDetailsResult.Member> collect = result.getGroupChat().getMemberList().stream().filter(e -> e.getType() == 2).collect(Collectors.toList());
+//        if(collect.isEmpty()){
+//            logger.info("群聊里面为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        Optional<QwGroupChatDetailsResult.Member> optional = collect.stream().filter(e -> e.getName().equals(user.getNickName()) || e.getName().equals(param.getNickName())).findFirst();
+//        if(!optional.isPresent()){
+//            logger.info("昵称未匹配上弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        QwGroupChatDetailsResult.Member member = optional.get();
+//        QwExternalContact qwExternalContact = qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>().eq("user_id", result.getGroupChat().getOwner()).eq("external_user_id", member.getUserId()));
+//        if(qwExternalContact==null){
+//            logger.info("外部联系人为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId()+":"+member.getUserId()+param.getNickName());
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        Long qwExternalId = qwExternalContact.getId();
+//        log.info("外部联系人数据:{}", qwExternalContact);
+////        addCompanyCompanyFsUser(param);
+//        FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(qwExternalId, param.getVideoId(),param.getQwUserId());
+//        if (log==null ){
+//            logger.info("看课记录为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId()+qwExternalId+":"+param.getVideoId()+":"+param.getQwUserId());
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        //判断外部联系人有没有绑定userId
+//        if (qwExternalContact.getFsUserId()!=null){
+//            //有客户有小程序id  但 登录的小程序id和根据外部联系人id查出来的小程序id不一致
+//            if (!qwExternalContact.getFsUserId().equals(param.getUserId())) {
+//                logger.info("小程序id不一致空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//                return addCustomerService(param.getQwUserId(),msg);
+//            }
+//            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByMiniUserId(param.getUserId());
+//            //匹配客户公司id
+//            if (qwExternalContacts.stream().noneMatch(contact -> contact.getCorpId().equals(param.getCorpId()))){
+//                logger.info("未匹配上公司空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//                return addCustomerService(param.getQwUserId(),msg);
+//            }
+//
+//            //看课记录中userId为0绑定userId
+//            if (log.getUserId()==null||log.getUserId().equals(0L) || !log.getUserId().equals(param.getUserId())){
+//                log.setUserId(param.getUserId());
+//            }
+//
+//            log.setUpdateTime(new Date());
+//            courseWatchLogMapper.updateFsCourseWatchLog(log);
+//
+//            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
+//        }else {
+//            //没绑定fsUser直接绑定fsUser
+//            QwExternalContact contact = new QwExternalContact();
+//            contact.setId(qwExternalId);
+//            contact.setFsUserId(param.getUserId());
+//            qwExternalContactMapper.updateQwExternalContact(contact);
+//            FsUser fsUser = new FsUser();
+//            fsUser.setUserId(user.getUserId());
+//            fsUser.setIsAddQw(1);
+//            fsUserMapper.updateFsUser(fsUser);
+//            //绑定上之后 更新观看记录
+//            //看课记录中userId为0绑定userId
+//            log.setUserId(param.getUserId());
+//            log.setUpdateTime(new Date());
+//            courseWatchLogMapper.updateFsCourseWatchLog(log);
+//        }
+//
+//        return R.error(567,"群聊通用链接").put("qwExternalId", qwExternalContact.getId());
     }
 
     private void createWatchLog(FsUserCourseVideoAddKfUParam param) {

+ 4 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java

@@ -39,6 +39,10 @@ import com.fs.his.vo.FsPrescribeVO;
 public interface IFsStoreOrderScrmService
 {
 
+    /**
+     * 同步物流信息到微信
+     */
+    void syncExpressToWx();
     /**
      * 查询订单
      *

+ 101 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -105,6 +105,12 @@ import com.fs.hisStore.enums.*;
 import com.fs.hisStore.service.*;
 import com.fs.system.service.ISysConfigService;
 import com.fs.wx.miniapp.config.WxMaProperties;
+import com.fs.wx.order.domain.FsWxExpressTask;
+import com.fs.wx.order.dto.*;
+import com.fs.wx.order.mapper.FsWxExpressTaskMapper;
+import com.fs.wx.order.service.ExpressToWxHolder;
+import com.fs.wx.order.service.ExpressToWxService;
+import com.fs.wx.order.service.ShippingService;
 import com.fs.ybPay.domain.OrderResult;
 import com.fs.ybPay.domain.RefundResult;
 import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
@@ -144,6 +150,7 @@ import java.sql.Timestamp;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
@@ -363,6 +370,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
     @Autowired
     private FsStoreOrderDfMapper fsStoreOrderDfMapper;
+    @Autowired
+    private ShippingService shippingService;
+    @Autowired
+    private FsWxExpressTaskMapper fsWxExpressTaskMapper;
 
     @PostConstruct
     public void initErpServiceMap() {
@@ -374,6 +385,81 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         erpServiceMap.put(5, jSTOrderService);     // 聚水潭
         erpServiceMap.put(6, k9OrderService);      // K9
     }
+    @Override
+    public void syncExpressToWx() {
+        List<FsWxExpressTask> fsWxExpressTasks = fsWxExpressTaskMapper.selectPendingData();
+        if (CollectionUtils.isEmpty(fsWxExpressTasks)) {
+            logger.info("当前没有待同步的数据!已取消");
+            return;
+        }
+
+        for (FsWxExpressTask fsWxExpressTask : fsWxExpressTasks) {
+
+            try{
+                UploadShippingInfoRequest request = new UploadShippingInfoRequest();
+
+                OrderKey orderKey = new OrderKey();
+                orderKey.setOrderNumberType(2);
+
+
+                FsUserScrm fsUser = userService.selectFsUserByUserId(fsWxExpressTask.getUserId());
+
+                ExpressToWxService service = ExpressToWxHolder.findBest(fsWxExpressTask.getType(),fsWxExpressTask.getOrderCode());
+                Asserts.notNull(service,"订单类型不被支持!");
+
+
+                orderKey.setTransactionId(service.getTransactionId());
+
+
+                String userPhone = service.getUserPhone();
+                String orderGoodsInfo = service.getOrderGoodsInfo();
+
+
+                Payer payer = new Payer();
+                if(StringUtils.isNotBlank(fsUser.getMaOpenId())){
+                    payer.setOpenid(fsUser.getMaOpenId());
+                }
+                request.setPayer(payer);
+                request.setOrderKey(orderKey);
+
+                request.setLogisticsType(1);
+                request.setDeliveryMode(1);
+
+                request.setShippingList(Collections.singletonList(ShippingItem.builder()
+                        .itemDesc(orderGoodsInfo)
+                        .expressCompany(service.getExpressCompany())
+                        .trackingNo(service.getExpressNo())
+                        .contact(Contact.builder().consignorContact(userPhone).build())
+                        .build()));
+
+                OffsetDateTime now = OffsetDateTime.now();
+                DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+                String formattedTimestamp = now.format(formatter);
+                request.setUploadTime(formattedTimestamp);
+
+
+                request.setAppid(fsWxExpressTask.getAppid());
+                WeChatApiResponse response = shippingService.uploadShippingInfo(request);
+                if(ObjectUtil.equal(response.getErrcode(),0)){
+                    fsWxExpressTask.setStatus(2);
+                } else {
+                    fsWxExpressTask.setRetryCount(fsWxExpressTask.getRetryCount() +1);
+                    fsWxExpressTask.setStatus(3);
+                    fsWxExpressTask.setData(JSON.toJSONString(request));
+                    fsWxExpressTask.setRequestBody(JSON.toJSONString(request));
+                    fsWxExpressTask.setResponseBody(JSON.toJSONString(response));
+                }
+            }catch (Exception e){
+                logger.info("该单 {} 推送到物流失败!",fsWxExpressTask);
+                fsWxExpressTask.setRetryCount(fsWxExpressTask.getRetryCount() +1);
+                fsWxExpressTask.setStatus(3);
+
+
+            }
+        }
+        fsWxExpressTaskMapper.batchUpdate(fsWxExpressTasks);
+
+    }
     /**
      * 查询订单
      *
@@ -1286,6 +1372,20 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     .templateType(TemplateListenEnum.TYPE_2.getValue())
                     .build();
             publisher.publishEvent(new TemplateEvent(this, templateBean));
+
+            List<FsStorePaymentScrm> fsStorePayments = fsStorePaymentMapper.selectFsStorePaymentByOrderId(order.getId());
+            FsStorePaymentScrm fsStorePayment = fsStorePayments.get(0);
+            FsWxExpressTask fsWxExpressTask = new FsWxExpressTask();
+            fsWxExpressTask.setUserId(order.getUserId());
+            fsWxExpressTask.setStatus(0);
+            fsWxExpressTask.setRetryCount(0);
+            fsWxExpressTask.setCreateTime(LocalDateTime.now());
+            fsWxExpressTask.setUpdateTime(LocalDateTime.now());
+            fsWxExpressTask.setOrderCode(order.getOrderCode());
+            fsWxExpressTask.setExpressCompany(express.getCode());
+            fsWxExpressTask.setExpressNo(deliveryId);
+            fsWxExpressTask.setAppid(fsStorePayment.getAppId());
+            fsWxExpressTaskMapper.insert(fsWxExpressTask);
         }
     }
 
@@ -3971,6 +4071,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 storePayment.setUserId(user.getUserId());
                 storePayment.setBusinessOrderId(order.getId().toString());
                 storePayment.setOrderId(order.getId());
+                storePayment.setAppId(fsPayConfig.getAppId() == null ? "" : fsPayConfig.getAppId());
                 fsStorePaymentMapper.insertFsStorePayment(storePayment);
 
                 if (fsPayConfig.getType().equals("hf")){

+ 0 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -890,7 +890,6 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
         storePayment.setOpenId(user.getMaOpenId());
         storePayment.setUserId(user.getUserId());
         storePayment.setPayMode("hf");//目前微信收款仅支持汇付
-        storePayment.setAppId(param.getAppId());
         fsStorePaymentMapper.insertFsStorePayment(storePayment);
 
         //汇付支付

+ 1 - 1
fs-service/src/main/java/com/fs/live/domain/LiveAutoTask.java

@@ -29,7 +29,7 @@ public class LiveAutoTask extends BaseEntity{
     @Excel(name = "任务名称")
     private String taskName;
 
-    /** 任务类型:1-定时推送卡片商品 2-定时发送红包 3-定时开启互动 */
+    /** 任务类型:1-定时推送卡片商品 2-定时发送红包 3-定时开启互动  4-抽奖 5-优惠券 6-自动上下架*/
     @Excel(name = "任务类型:1-定时推送卡片商品 2-定时发送红包 3-定时开启互动 4-抽奖")
     private Long taskType;
 

+ 8 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -133,10 +133,16 @@ public interface LiveMapper
     @Update("update live set global_visible = #{status} where live_id = #{liveId}")
     void updateGlobalVisible(@Param("liveId")Long liveId,@Param("status") Integer status);
 
-    @Select("select * from live where company_id = #{companyId} and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1")
+    @Select({"<script>" +
+            "select * from live where 1=1 " +
+            " <if test='companyId!=null' > and company_id = #{companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
+            " </script>"})
     List<Live> listLiveData(@Param("companyId")Long companyId);
 
-    @Select("select count(1) from live where company_id = #{companyId} and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1")
+    @Select({"<script>" +
+            "select count(1) from live where 1=1 " +
+            " <if test='companyId!=null' > and company_id = #{companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
+            " </script>"})
     int listLiveDataCount(@Param("companyId") Long companyId);
 
 

+ 12 - 11
fs-service/src/main/java/com/fs/live/param/LiveDataParam.java

@@ -1,6 +1,7 @@
 package com.fs.live.param;
 
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
@@ -19,31 +20,31 @@ public class LiveDataParam {
 
     private Long liveId;
 
-   /* *//** 直播名称 *//*
+
     @Excel(name = "直播名称")
     private String liveName;
 
-    *//** 直播封面 *//*
+
     @Excel(name = "直播封面")
     private String liveImgUrl;
 
-    *//** 1待直播 2直播中 3已结束 *//*
+
     @Excel(name = "1待直播 2直播中 3已结束")
     private Integer status;
 
-    *//** 开始时间 *//*
-    @JsonFormat(pattern = "yyyy-MM-dd")
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date startTime;
 
-    *//** 结束时间 *//*
-    @JsonFormat(pattern = "yyyy-MM-dd")
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date finishTime;
 
-    *//** 直播地址 *//*
+
     @Excel(name = "直播地址")
-    private String rtmpUrl;*/
+    private String rtmpUrl;
 
     /** 浏览量 */
     @Excel(name = "浏览量")
@@ -84,9 +85,9 @@ public class LiveDataParam {
 
     /** 观看时长 */
     private Integer watchDuration;
-    /** 开始时间 */
-    private Date startTime;
     /** 结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date endTime;
 
 

+ 1 - 1
fs-service/src/main/java/com/fs/live/service/ILiveAutoTaskService.java

@@ -53,7 +53,7 @@ public interface ILiveAutoTaskService {
      * @param liveAutoTask 直播间自动化任务配置
      * @return 结果
      */
-    int updateLiveAutoTask(LiveAutoTask liveAutoTask);
+    R updateLiveAutoTask(LiveAutoTask liveAutoTask);
 
     /**
      * 批量删除直播间自动化任务配置

+ 26 - 21
fs-service/src/main/java/com/fs/live/service/impl/LiveAutoTaskServiceImpl.java

@@ -22,6 +22,8 @@ import com.fs.live.service.ILiveGoodsService;
 import com.fs.live.vo.LiveGoodsVo;
 import com.fs.live.vo.LiveLotteryProductListVo;
 import org.checkerframework.checker.units.qual.A;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -34,6 +36,7 @@ import org.springframework.stereotype.Service;
 @Service
 public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
 
+    private static final Logger log = LoggerFactory.getLogger(LiveAutoTaskServiceImpl.class);
     @Autowired
     private LiveMapper liveMapper;
     @Autowired
@@ -249,59 +252,59 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
      * @return 结果
      */
     @Override
-    public int updateLiveAutoTask(LiveAutoTask liveAutoTask)
+    public R updateLiveAutoTask(LiveAutoTask liveAutoTask)
     {
         LiveAutoTask existTask = baseMapper.selectLiveAutoTaskById(liveAutoTask.getId());
         redisCache.redisTemplate.opsForZSet().removeRangeByScore("live:auto_task:" + existTask.getLiveId(), existTask.getAbsValue().getTime(), existTask.getAbsValue().getTime());
         if (liveAutoTask.getTaskType() == 1L) {
             // 商品
             LiveGoodsVo liveGoodsVo = goodsService.selectLiveGoodsVoByGoodsId(Long.valueOf(liveAutoTask.getContent()));
-            if(liveGoodsVo == null) return -1;
+            if(liveGoodsVo == null) return R.error("商品不存在");
             liveAutoTask.setContent(JSON.toJSONString(liveGoodsVo));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+             baseMapper.updateLiveAutoTask(liveAutoTask);
         }else if (liveAutoTask.getTaskType() == 2L) {
             // 红包
             LiveRedConf liveRedConf = liveRedConfMapper.selectLiveRedConfByRedId(Long.valueOf(liveAutoTask.getContent()));
-            if(liveRedConf == null) return -1;
-            if(liveRedConf.getRedStatus() != 0L) return -1;
+            if(liveRedConf == null) return R.error("红包不存在!");
+            if(liveRedConf.getRedStatus() != 0L) return R.error("红包已结束!");
             liveAutoTask.setContent(JSON.toJSONString(liveRedConf));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         }else if (liveAutoTask.getTaskType() == 4L) {
             // 开启抽奖
             LiveLotteryConf liveLotteryConf = liveLotteryConfMapper.selectLiveLotteryConfByLotteryId(Long.valueOf(liveAutoTask.getContent()));
-            if(liveLotteryConf == null) return -1;
-            if(!"0".equals(liveLotteryConf.getLotteryStatus())) return -1;
+            if(liveLotteryConf == null) return R.error("抽奖不存在!");
+            if(!"0".equals(liveLotteryConf.getLotteryStatus())) return R.error("抽奖未开始!");
             List<LiveLotteryProduct> prizes = lotteryProductConfMapper.selectLiveLotteryProductConfByLotteryId(liveLotteryConf.getLotteryId());
             if (prizes == null || prizes.isEmpty()) {
-                return -1;
+                return R.error("请先添加奖品");
             }
             liveAutoTask.setContent(JSON.toJSONString(liveLotteryConf));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         } else if(liveAutoTask.getTaskType() == 3L){
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         } else if( liveAutoTask.getTaskType() == 5L){
             // 自动优惠券
             LiveCoupon liveCoupon = liveCouponMapper.selectLiveCouponById(Long.valueOf(liveAutoTask.getContent()));
-            if(liveCoupon == null) return -1;
+            if(liveCoupon == null) return R.error("优惠券不存在!");
             LiveCouponIssue liveCouponIssue = liveCouponIssueMapper.selectLiveCouponIssueByCouponId(liveCoupon.getCouponId());
-            if(liveCouponIssue == null)return -1;
+            if(liveCouponIssue == null)return R.error("未发布优惠券!");
             LiveCouponIssueRelation liveCouponIssueRelation = liveCouponMapper.selectCouponRelation(liveAutoTask.getLiveId(),liveCouponIssue.getId());
-            if(liveCouponIssueRelation == null) return -1;
-            if(ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) return -1;
+            if(liveCouponIssueRelation == null) return R.error("未绑定商品,无法发布!");
+            if(ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) return R.error("未绑定商品,无法发布!");
             liveCoupon.setGoodsId(liveCouponIssueRelation.getGoodsId());
             liveAutoTask.setContent(JSON.toJSONString(liveCoupon));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         } else if (liveAutoTask.getTaskType() == 6L) {
             // 上架/下架商品
             try {
                 com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(liveAutoTask.getContent());
                 Long goodsId = jsonObject.getLong("goodsId");
                 Integer status = jsonObject.getInteger("status");
-                if (goodsId == null || status == null) return -1;
+                if (goodsId == null || status == null) return R.error("商品ID或状态为空");
 
                 // 查询商品信息
                 LiveGoodsVo liveGoodsVo = goodsService.selectLiveGoodsVoByGoodsId(goodsId);
-                if(liveGoodsVo == null) return -1;
+                if(liveGoodsVo == null) return R.error("商品不存在");
 
                 // 保存商品信息和上下架状态
                 com.alibaba.fastjson.JSONObject content = new com.alibaba.fastjson.JSONObject();
@@ -310,13 +313,15 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
                 content.put("productName", liveGoodsVo.getProductName());
                 content.put("status", status);
                 liveAutoTask.setContent(content.toJSONString());
-                return baseMapper.updateLiveAutoTask(liveAutoTask);
+                 baseMapper.updateLiveAutoTask(liveAutoTask);
             } catch (Exception e) {
-                return -1;
+                log.error("上架/下架商品自动化任务,更新异常!" + e.getMessage());
+                return R.error("上架/下架商品自动化任务,更新异常!");
             }
         } else {
-            return -1;
+            return R.error("任务类型错误");
         }
+        return R.ok("更新成功");
     }
 
     /**

+ 20 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -94,6 +94,8 @@ import com.fs.live.service.*;
 import com.fs.live.vo.*;
 import com.fs.store.domain.*;
 import com.fs.system.service.ISysConfigService;
+import com.fs.wx.order.domain.FsWxExpressTask;
+import com.fs.wx.order.mapper.FsWxExpressTaskMapper;
 import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
@@ -270,6 +272,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Autowired
     private LiveAfterSalesMapper liveAfterSalesMapper;
 
+    @Autowired
+    private FsWxExpressTaskMapper fsWxExpressTaskMapper;
+
 
     //ERP 类型到服务的映射
     private Map<Integer, IErpOrderService> erpServiceMap;
@@ -1706,6 +1711,20 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                     .templateType(TemplateListenEnum.TYPE_2.getValue())
                     .build();
             publisher.publishEvent(new TemplateEvent(this, templateBean));
+
+            LiveOrderPayment fsStorePayment  = liveOrderPaymentMapper.selectLiveOrderLatestPayByOrderId(order.getOrderId());
+            FsWxExpressTask fsWxExpressTask = new FsWxExpressTask();
+            fsWxExpressTask.setUserId(Long.valueOf(order.getUserId()));
+            fsWxExpressTask.setStatus(0);
+            fsWxExpressTask.setRetryCount(0);
+            fsWxExpressTask.setType(1);
+            fsWxExpressTask.setCreateTime(LocalDateTime.now());
+            fsWxExpressTask.setUpdateTime(LocalDateTime.now());
+            fsWxExpressTask.setOrderCode(order.getOrderCode());
+            fsWxExpressTask.setExpressCompany(express.getCode());
+            fsWxExpressTask.setExpressNo(deliveryId);
+            fsWxExpressTask.setAppid(fsStorePayment.getAppId());
+            fsWxExpressTaskMapper.insert(fsWxExpressTask);
         }
     }
 
@@ -2835,6 +2854,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 storePayment.setOpenId(user.getRealName());
                 storePayment.setUserId(user.getUserId());
                 storePayment.setBusinessId(String.valueOf(order.getOrderId()));
+                storePayment.setAppId(fsPayConfig.getAppId() == null ? "" : fsPayConfig.getAppId());
                 liveOrderPaymentMapper.insertLiveOrderPayment(storePayment);
 
                 if (fsPayConfig.getType().equals("hf")){

+ 1 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -726,7 +726,7 @@ public class LiveServiceImpl implements ILiveService
         if (deleteCount > 0) {
             return R.ok("操作成功");
         }
-        return R.error();
+        return R.error("删除失败");
     }
 
     @Override

+ 141 - 0
fs-service/src/main/java/com/fs/pay/domain/PaymentMiniProgramConfig.java

@@ -0,0 +1,141 @@
+package com.fs.pay.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 多小程序支付配置对象 payment_mini_program_config
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PaymentMiniProgramConfig extends BaseEntity{
+
+    /** 主键ID */
+    private Long id;
+
+    /** 支付类型:YB-易宝,TZH-台州银行,WX-微信,HF-汇付 */
+    @Excel(name = "支付类型:YB-易宝,TZH-台州银行,WX-微信,HF-汇付")
+    private String payType;
+
+    /**
+     * 小程序名称
+     */
+    @Excel(name = "小程序名称")
+    private String appName;
+    /** 小程序appid */
+    @Excel(name = "小程序appid")
+    private String appid;
+    /**
+     * 小程序密钥
+     */
+    @Excel(name = "小程序密钥")
+    private String appSecret;
+
+    /** 易宝商户号 */
+    @Excel(name = "易宝商户号")
+    private String ybMerchantNo;
+
+    /** 易宝Key */
+    @Excel(name = "易宝Key")
+    private String ybKey;
+
+    /** 易宝回调地址 */
+    @Excel(name = "易宝回调地址")
+    private String ybNotifyUrl;
+
+    /** 台州银行商户号 */
+    @Excel(name = "台州银行商户号")
+    private String tzhMerchantNo;
+
+    /** 台州appSecret */
+    @Excel(name = "台州appSecret")
+    private String tzhAppsecret;
+
+    /** 台州私钥 */
+    @Excel(name = "台州私钥")
+    private String tzhPrivateKey;
+
+    /** 台州平台公钥 */
+    @Excel(name = "台州平台公钥")
+    private String tzhPublicKey;
+
+    /** 台州appKey */
+    @Excel(name = "台州appKey")
+    private String tzhAppkey;
+
+    /** 台州支付回调地址 */
+    @Excel(name = "台州支付回调地址")
+    private String tzhPayNotifyUrl;
+
+    /** 台州退款回调地址 */
+    @Excel(name = "台州退款回调地址")
+    private String tzhRefundNotifyUrl;
+
+    /** 台州分账回调地址 */
+    @Excel(name = "台州分账回调地址")
+    private String tzhSplitNotifyUrl;
+
+    /** 微信商户号 */
+    @Excel(name = "微信商户号")
+    private String wxMerchantNo;
+
+    /** 微信Key */
+    @Excel(name = "微信Key")
+    private String wxKey;
+    /**
+     *
+     */
+    private String wxKeyPath;
+    /**
+     * 微信回调url
+     */
+    @Excel(name = "微信回调url")
+    private String wxNotifyUrl;
+
+    /** 汇付产品号 */
+    @Excel(name = "汇付产品号")
+    private String hfProductNo;
+
+    /** 汇付系统号 */
+    @Excel(name = "汇付系统号")
+    private String hfSystemNo;
+
+    /** 汇付商户号 */
+    @Excel(name = "汇付商户号")
+    private String hfMerchantNo;
+
+    /** 汇付服务商私钥 */
+    @Excel(name = "汇付服务商私钥")
+    private String hfPrivateKey;
+
+    /** 汇付公钥 */
+    @Excel(name = "汇付公钥")
+    private String hfPublicKey;
+
+    /** 汇付支付回调地址 */
+    @Excel(name = "汇付支付回调地址")
+    private String hfPayNotifyUrl;
+
+    /** 汇付大额支付回调地址 */
+    @Excel(name = "汇付大额支付回调地址")
+    private String hfLargePayNotifyUrl;
+
+    /** 汇付退款回调地址 */
+    @Excel(name = "汇付退款回调地址")
+    private String hfRefundNotifyUrl;
+
+    /** 汇付大额退款回调地址 */
+    @Excel(name = "汇付大额退款回调地址")
+    private String hfLargeRefundNotifyUrl;
+
+    /** 状态,1启用,0禁用 */
+    @Excel(name = "状态,1启用,0禁用")
+    private Integer status;
+
+
+}

+ 70 - 0
fs-service/src/main/java/com/fs/pay/mapper/PaymentMiniProgramConfigMapper.java

@@ -0,0 +1,70 @@
+package com.fs.pay.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.pay.domain.PaymentMiniProgramConfig;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 多小程序支付配置Mapper接口
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+public interface PaymentMiniProgramConfigMapper extends BaseMapper<PaymentMiniProgramConfig>{
+    /**
+     * 查询多小程序支付配置
+     *
+     * @param id 多小程序支付配置主键
+     * @return 多小程序支付配置
+     */
+    PaymentMiniProgramConfig selectPaymentMiniProgramConfigById(String id);
+
+    /**
+     * 查询多小程序支付配置列表
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 多小程序支付配置集合
+     */
+    List<PaymentMiniProgramConfig> selectPaymentMiniProgramConfigList(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 新增多小程序支付配置
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 结果
+     */
+    int insertPaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 修改多小程序支付配置
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 结果
+     */
+    int updatePaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 删除多小程序支付配置
+     *
+     * @param id 多小程序支付配置主键
+     * @return 结果
+     */
+    int deletePaymentMiniProgramConfigById(String id);
+
+    /**
+     * 批量删除多小程序支付配置
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deletePaymentMiniProgramConfigByIds(String[] ids);
+
+    PaymentMiniProgramConfig selectPaymentConfigByAppId(@Param("appid") String appid);
+
+    @Select("select * from payment_mini_program_config where status = 1")
+    List<PaymentMiniProgramConfig> selectAll();
+
+}

+ 104 - 0
fs-service/src/main/java/com/fs/wx/order/domain/FsWxExpressTask.java

@@ -0,0 +1,104 @@
+package com.fs.wx.order.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 微信同步发货信息定时任务表
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class FsWxExpressTask {
+
+    /**
+     * 任务ID,唯一标识
+     */
+    private Long id;
+
+    /**
+     * 订单code
+     */
+    private String orderCode;
+
+    /**
+     * 支付单号
+     */
+    private String payCode;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 消息内容,JSON格式。
+     */
+    private String data;
+
+    /**
+     * 任务状态:0=待执行, 1=执行中, 2=执行成功, 3=执行失败, 4=已取消
+     */
+    private Integer status;
+
+    /**
+     * 当前重试次数
+     */
+    private Integer retryCount;
+
+    /**
+     * 最大重试次数
+     */
+    private Integer maxRetries;
+
+    /**
+     * 请求参数(JSON格式,主要记录 access_token 获取方式)
+     */
+    private String requestParams;
+
+    /**
+     * 完整的请求体 (JSON格式)
+     */
+    private String requestBody;
+
+    /**
+     * API 响应结果 (JSON格式)
+     */
+    private String responseBody;
+
+    /**
+     * 错误信息 (如果执行失败)
+     */
+    private String errorMessage;
+
+    /**
+     * 任务创建时间
+     */
+    private LocalDateTime createTime; // 使用LocalDateTime对应timestamp
+
+    /**
+     * 最后更新时间
+     */
+    private LocalDateTime updateTime; // 使用LocalDateTime对应timestamp
+
+
+    /**
+     * 快递公司
+     */
+    private String expressCompany;
+
+    /**
+     * 快递编号
+     */
+    private String expressNo;
+
+    private Integer type;
+
+    /**
+     * 小程序ID
+     */
+    private String appid;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wx/order/dto/Contact.java

@@ -0,0 +1,21 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Contact {
+    // 根据实际需要添加更多联系人字段
+    @JsonProperty("consignor_contact")
+    private String consignorContact; // 发货人联系方式 (示例字段)
+
+    // 可以添加收货人联系方式等
+    // @JsonProperty("receiver_contact")
+    // private String receiverContact;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/wx/order/dto/OrderKey.java

@@ -0,0 +1,27 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderKey {
+
+    @JsonProperty("order_number_type")
+    private Integer orderNumberType;
+
+    @JsonProperty("transaction_id")
+    private String transactionId;
+
+    @JsonProperty("mchid")
+    private String mchId;
+
+    @JsonProperty("out_trade_no")
+    private String outTradeNo;
+}

+ 77 - 0
fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryRequest.java

@@ -0,0 +1,77 @@
+package com.fs.wx.order.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderQueryRequest {
+
+    /**
+     * 聚合账户 (Required)
+     */
+    private String account;
+
+    /**
+     * 下游订单号 (Required if upOrderId is null)
+     */
+    private String lowOrderId;
+
+    /**
+     * 通莞订单号 (Required if lowOrderId is null)
+     */
+    private String upOrderId;
+
+    /**
+     * 值为"Y"时,接口返回优惠信息字段 (Optional)
+     */
+    private String extendInfo;
+
+    /**
+     * 值为"1"时,接口返回易宝专业版分账订单详情 (Optional)
+     */
+    private String isExtend;
+
+    /**
+     * 值为"1"时,接口返回SAAS分账订单详情 (Optional)
+     */
+    private String isNeedUpInfo;
+
+    /**
+     * 值为"1"时,接口返回渠道商户订单号字段 (Optional)
+     */
+    private String isNeedChannelMchOrderId;
+
+    /**
+     * 值为"1"时,返回因公付金额信息,仅支付宝服务商渠道支持 (Optional)
+     */
+    private String isNeedEnterprisePayInfo;
+
+    /**
+     * 签名 (Required)
+     * Note: This will be calculated and set by the service.
+     */
+    private String sign;
+
+    /**
+     * Helper method to get parameters for signing.
+     * Excludes the 'sign' field itself.
+     * Returns sorted map to ensure consistent order for signing.
+     *
+     * @return A map of non-null parameters sorted by key.
+     */
+    public Map<String, String> toSignMap() {
+       Map<String,String> sign = new HashMap<>();
+        sign.put("account",account);
+        sign.put("upOrderId",upOrderId);
+        sign.put("isNeedUpInfo", isNeedUpInfo);
+        return sign;
+    }
+}

+ 236 - 0
fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryResponse.java

@@ -0,0 +1,236 @@
+package com.fs.wx.order.dto;
+
+import cn.hutool.core.annotation.Alias;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class OrderQueryResponse {
+
+    /**
+     * 100:成功,101:失败 (Required)
+     */
+    private Integer status;
+
+    /**
+     * 消息描述 (Required)
+     */
+    private String message;
+
+    /**
+     * 上游订单号 (Required)
+     */
+    private String channelOrderId;
+
+    /**
+     * 通莞订单号 (Required)
+     */
+    private String upOrderId;
+
+    /**
+     * 默认传null (Required) - Although API doc says required, it seems informational.
+     */
+    private String payoffType;
+
+    /**
+     * 支付时间 (Required)
+     */
+    private String payTime;
+
+    /**
+     * WX: openid; Alipay: account name (Required)
+     * Mapped from both "openId" and "openid" in JSON
+     */
+    @Alias("openid") // Instruct Hutool to map "openid" to this field as well
+    private String openId;
+
+    /**
+     * 签名 (Required) - For response validation
+     */
+    private String sign;
+
+    /**
+     * 结算渠道编号 (Required)
+     */
+    private String settlementChannel;
+
+    /**
+     * 下游订单号 (Required)
+     */
+    private String lowOrderId;
+
+    /**
+     * 支付金额,单位元 (Required)
+     */
+    private String payMoney; // Keep as String as per API doc, can be converted later
+
+    /**
+     * 支付方式 0:WX, 1:ZFB, 2:UnionPay QR, 6:LongPay, 8:BestPay, H:Digital Currency (Required)
+     */
+    private String payType;
+
+    /**
+     * 0:Success, 1:Fail, 2:Revoked, 4:Pending, 5:Refunded, 6:Partial Refund (Required)
+     */
+    private String state;
+
+    /**
+     * 订单备注 (Optional)
+     */
+    private String attach;
+
+    /**
+     * 聚合账户 (Required)
+     */
+    private String account;
+
+    /**
+     * 支付方式例:WX、ZFB、YZF、LZF、YLZF (Required)
+     */
+    private String channelId;
+
+    /**
+     * 渠道优惠金额 JSON String, e.g., {"discountAmt":"100"} (Unit: Fen) (Optional)
+     */
+    private String discountInfo;
+
+    /**
+     * 扩展信息 JSON String (Optional, if requested)
+     * Need to be parsed into an object if needed.
+     */
+    private String extendInfo; // Raw JSON string
+
+    /**
+     * 易宝/SAAS分账详情 JSON Object (Optional, if requested)
+     */
+    private JSONObject extend; // Parsed JSON Object
+
+    /**
+     * SAAS分账订单详情 JSON Object (Optional, if requested)
+     */
+    private JSONObject upInfo; // Parsed JSON Object
+
+    /**
+     * 渠道商户订单号 (Optional, if requested)
+     */
+    private String channelMchOrderId;
+
+    /**
+     * 订单管控状态 FROZEN/UN_FROZEN (Optional, if requested via isNeedUpInfo=1)
+     */
+    private String fundControlCsStatus;
+
+    /**
+     * 管控订单解冻时间 yyyy-mm-dd hh:mm:ss (Optional, if requested via isNeedUpInfo=1 and status is UN_FROZEN)
+     */
+    private String csUnFrozenCompleteDate;
+
+    /**
+     * 因公付金额信息 JSON String, e.g., {"invoiceAmount":"0.01","isUseEnterprisePay":false} (Optional, if requested)
+     */
+    private String enterprisePayInfo; // Raw JSON String
+
+    // --- Helper methods to access parsed nested JSON data ---
+
+    /**
+     * Gets parsed DiscountInfo object from discountInfo string.
+     * @return DiscountInfo object or null if parsing fails or discountInfo is null/empty.
+     */
+    public DiscountInfo getParsedDiscountInfo() {
+        if (JSONUtil.isJson(this.discountInfo)) {
+            try {
+                return JSONUtil.toBean(this.discountInfo, DiscountInfo.class);
+            } catch (Exception e) {
+                // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+     /**
+     * Gets parsed EnterprisePayInfo object from enterprisePayInfo string.
+     * @return EnterprisePayInfo object or null if parsing fails or enterprisePayInfo is null/empty.
+     */
+    public EnterprisePayInfo getParsedEnterprisePayInfo() {
+         if (JSONUtil.isJson(this.enterprisePayInfo)) {
+            try {
+                return JSONUtil.toBean(this.enterprisePayInfo, EnterprisePayInfo.class);
+            } catch (Exception e) {
+                 // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets parsed ExtendInfo object from extendInfo string.
+     * @return JSONObject or null if parsing fails or extendInfo is null/empty.
+     */
+    public JSONObject getParsedExtendInfo() {
+        if (JSONUtil.isJson(this.extendInfo)) {
+            try {
+                return JSONUtil.parseObj(this.extendInfo);
+            } catch (Exception e) {
+                 // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+    // --- Nested DTOs for parsed JSON strings ---
+
+    @Data
+    public static class DiscountInfo {
+        /**
+         * Discount amount in Fen (分)
+         */
+        private String discountAmt; // Keep as String as per API, or use Integer/Long
+
+        public BigDecimal getDiscountAmtYuan() {
+            if (discountAmt != null) {
+                try {
+                    // Assuming discountAmt is in Fen (cents)
+                    return new BigDecimal(discountAmt).divide(new BigDecimal("100"));
+                } catch (NumberFormatException e) {
+                    return null; // Or handle error appropriately
+                }
+            }
+            return null;
+        }
+    }
+
+    @Data
+    public static class EnterprisePayInfo {
+        private String invoiceAmount; // Amount in Yuan (元)
+        private Boolean isUseEnterprisePay;
+
+        public BigDecimal getInvoiceAmountValue() {
+             if (invoiceAmount != null) {
+                 try {
+                     return new BigDecimal(invoiceAmount);
+                 } catch (NumberFormatException e) {
+                     return null; // Or handle error appropriately
+                 }
+             }
+             return null;
+         }
+    }
+
+    /**
+     * Validates the response signature.
+     * IMPORTANT: Implement the actual signature validation logic based on Tongguan's specification.
+     * @param secretKey The secret key.
+     * @return true if the signature is valid, false otherwise.
+     */
+    public boolean isResponseSignValid(String secretKey) {
+
+        System.err.println("WARN: Response signature validation is not implemented!");
+        return true;
+    }
+}

+ 17 - 0
fs-service/src/main/java/com/fs/wx/order/dto/Payer.java

@@ -0,0 +1,17 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Payer {
+
+    @JsonProperty("openid")
+    private String openid;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/wx/order/dto/ShippingItem.java

@@ -0,0 +1,26 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShippingItem {
+
+    @JsonProperty("tracking_no")
+    private String trackingNo;
+
+    @JsonProperty("express_company")
+    private String expressCompany;
+
+    @JsonProperty("item_desc")
+    private String itemDesc;
+
+    @JsonProperty("contact")
+    private Contact contact;
+}

+ 42 - 0
fs-service/src/main/java/com/fs/wx/order/dto/UploadShippingInfoRequest.java

@@ -0,0 +1,42 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UploadShippingInfoRequest {
+
+    @JsonProperty("order_key")
+    private OrderKey orderKey;
+
+    @JsonProperty("logistics_type")
+    private Integer logisticsType;
+
+    @JsonProperty("delivery_mode")
+    private Integer deliveryMode;
+
+    @JsonProperty("is_all_delivered")
+    private Boolean isAllDelivered;
+
+    @JsonProperty("shipping_list")
+    private List<ShippingItem> shippingList;
+
+    @JsonProperty("upload_time")
+    private String uploadTime;
+
+    @JsonProperty("payer")
+    private Payer payer;
+
+    @JsonIgnore
+    private String appid;
+
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiConfig.java

@@ -0,0 +1,21 @@
+package com.fs.wx.order.dto;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Getter
+public class WeChatApiConfig {
+
+    @Value("${wechat.api.base-url}")
+    private String baseUrl;
+
+    @Value("${wechat.api.upload-shipping-info}")
+    private String uploadShippingInfoPath;
+
+
+    public String getUploadShippingInfoUrl() {
+        return baseUrl + uploadShippingInfoPath;
+    }
+}

+ 20 - 0
fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiResponse.java

@@ -0,0 +1,20 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class WeChatApiResponse {
+
+    @JsonProperty("errcode")
+    private Integer errcode;
+
+    @JsonProperty("errmsg")
+    private String errmsg;
+
+    public boolean isSuccess() {
+        return errcode != null && errcode == 0;
+    }
+}

+ 117 - 0
fs-service/src/main/java/com/fs/wx/order/mapper/FsWxExpressTaskMapper.java

@@ -0,0 +1,117 @@
+package com.fs.wx.order.mapper;
+
+import com.fs.wx.order.domain.FsWxExpressTask;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * 微信同步发货信息定时任务表 Mapper 接口
+ */
+@Mapper
+public interface FsWxExpressTaskMapper {
+
+    /**
+     * 根据ID查询任务
+     * @param id 任务ID
+     * @return FsWxExpressTask 任务实体
+     */
+    @Select("SELECT * FROM f s_wx_express_task WHERE id = #{id}")
+    FsWxExpressTask selectById(@Param("id") Long id);
+
+    /**
+     * 插入新任务
+     * @param task 任务实体
+     * @return 影响行数
+     */
+    @Insert("INSERT INTO fs_wx_express_task (order_code, user_id, data, status, retry_count, max_retries, request_params, request_body, response_body, error_message, create_time, update_time,express_company,express_no,type,appid) " +
+            "VALUES (#{orderCode}, #{userId}, #{data}, #{status}, #{retryCount}, #{maxRetries}, #{requestParams}, #{requestBody}, #{responseBody}, #{errorMessage}, #{createTime}, #{updateTime},#{expressCompany},#{expressNo},#{type},#{appid})")
+    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
+    int insert(FsWxExpressTask task);
+
+    /**
+     * 根据ID更新任务信息
+     * @param task 任务实体
+     * @return 影响行数
+     */
+    @Update("<script>" +
+            "UPDATE fs_wx_express_task " +
+            "<set>" +
+            "  <if test='orderCode != null'>order_code = #{orderCode},</if>" +
+            "  <if test='userId != null'>user_id = #{userId},</if>" +
+            "  <if test='data != null'>data = #{data}::json,</if>" +
+            "  <if test='status != null'>status = #{status},</if>" +
+            "  <if test='retryCount != null'>retry_count = #{retryCount},</if>" +
+            "  <if test='maxRetries != null'>max_retries = #{maxRetries},</if>" +
+            "  <if test='requestParams != null'>request_params = #{requestParams},</if>" +
+            "  <if test='requestBody != null'>request_body = #{requestBody},</if>" +
+            "  <if test='responseBody != null'>response_body = #{responseBody},</if>" +
+            "  <if test='errorMessage != null'>error_message = #{errorMessage},</if>" +
+            "  <if test='type != null'>type = #{type},</if>" +
+            "</set>" +
+            "WHERE id = #{id}" +
+            "</script>")
+    int updateById(FsWxExpressTask task);
+
+    /**
+     * 根据ID删除任务
+     * @param id 任务ID
+     * @return 影响行数
+     */
+    @Delete("DELETE FROM fs_wx_express_task WHERE id = #{id}")
+    int deleteById(@Param("id") Long id);
+
+    /**
+     * 根据状态查询任务列表
+     * @param status 任务状态
+     * @return List<FsWxExpressTask> 任务列表
+     */
+    @Select("SELECT * FROM fs_wx_express_task WHERE status = #{status}")
+    List<FsWxExpressTask> selectByStatus(@Param("status") Integer status);
+
+
+    /**
+     * 查询待处理数据
+     * @return
+     */
+    @Select("SELECT * FROM fs_wx_express_task WHERE retry_count < 3 AND status in (0,3)")
+    List<FsWxExpressTask> selectPendingData();
+    @Update("<script>" +
+            "<foreach collection='list' item='task' separator=';'>" +
+            " UPDATE fs_wx_express_task" +
+            " SET" +
+            "  order_code = #{task.orderCode}," +
+            "  user_id = #{task.userId}," +
+            "  data = #{task.data}," +
+            "  status = #{task.status}," +
+            "  retry_count = #{task.retryCount}," +
+            "  max_retries = #{task.maxRetries}," +
+            "  request_params = #{task.requestParams}," +
+            "  request_body = #{task.requestBody}," +
+            "  response_body = #{task.responseBody}," +
+            "  error_message = #{task.errorMessage}," +
+            "  update_time = now()" +
+            " WHERE id = #{task.id}" +
+            "</foreach>" +
+            "</script>")
+    void batchUpdate(List<FsWxExpressTask> fsWxExpressTasks);
+
+    /**
+     * 批量插入新任务
+     * @param tasks 任务实体列表
+     * @return 影响行数 (Optional: change void to int if needed)
+     */
+    @Insert("<script>" +
+            "INSERT INTO fs_wx_express_task " +
+            "(order_code, user_id, data, status, retry_count, max_retries, request_params, request_body, response_body, error_message, create_time, update_time, pay_code,appid) " +
+            "VALUES " +
+            "<foreach collection='tasks' item='task' separator=','>" +
+            "(" +
+            "#{task.orderCode}, #{task.userId}, #{task.data}, #{task.status}, #{task.retryCount}, #{task.maxRetries}, " + // Keep ::json if PostgreSQL
+            "#{task.requestParams}, #{task.requestBody}, #{task.responseBody}, #{task.errorMessage}, " +
+            "#{task.createTime}, #{task.updateTime},#{task.payCode},#{task.appid}" +
+            ")" +
+            "</foreach>" +
+            "</script>")
+    void insertBatch(List<FsWxExpressTask> tasks);
+}

+ 30 - 0
fs-service/src/main/java/com/fs/wx/order/service/ExpressToWxHolder.java

@@ -0,0 +1,30 @@
+package com.fs.wx.order.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class ExpressToWxHolder {
+    private static ApplicationContext applicationContext;
+
+    @Autowired
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        ExpressToWxHolder.applicationContext = applicationContext;
+    }
+
+    public static ExpressToWxService findBest(Integer type,String orderCode) {
+        String[] beanNames = applicationContext.getBeanNamesForType(ExpressToWxService.class);
+
+        for (String beanName : beanNames) {
+            ExpressToWxService handler = (ExpressToWxService) applicationContext.getBean(beanName);
+            if (handler.support(type)) {
+                handler.setOrderCode(orderCode);
+                return handler;
+            }
+        }
+        throw new IllegalArgumentException(String.format(String.format("对应类型 %d 没有被找到!",type)));
+    }
+}

+ 27 - 0
fs-service/src/main/java/com/fs/wx/order/service/ExpressToWxService.java

@@ -0,0 +1,27 @@
+package com.fs.wx.order.service;
+
+import lombok.Getter;
+
+@Getter
+public abstract class ExpressToWxService {
+
+    private String orderCode;
+
+
+    public void setOrderCode(String orderCode) {
+        this.orderCode = orderCode;
+    }
+
+    public abstract String getTransactionId();
+
+
+    public abstract String getUserPhone();
+
+
+    public abstract String getOrderGoodsInfo();
+
+    public abstract String getExpressCompany();
+    public abstract String getExpressNo();
+
+    public abstract boolean support(Integer type);
+}

+ 20 - 0
fs-service/src/main/java/com/fs/wx/order/service/OrderQueryService.java

@@ -0,0 +1,20 @@
+package com.fs.wx.order.service;
+
+
+import com.fs.wx.order.dto.OrderQueryRequest;
+import com.fs.wx.order.dto.OrderQueryResponse;
+
+public interface OrderQueryService {
+
+    /**
+     * Queries an order using either lowOrderId or upOrderId.
+     *
+     * @param request The request object containing query parameters.
+     *                The 'sign' field will be calculated internally.
+     * @return The order query response.
+     * @throws IllegalArgumentException if required parameters are missing.
+     * @throws RuntimeException if the API call fails or returns an error status.
+     */
+    OrderQueryResponse queryOrder(OrderQueryRequest request);
+
+}

+ 128 - 0
fs-service/src/main/java/com/fs/wx/order/service/ShippingService.java

@@ -0,0 +1,128 @@
+package com.fs.wx.order.service;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.*;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.wx.order.dto.UploadShippingInfoRequest;
+import com.fs.wx.order.dto.WeChatApiConfig;
+import com.fs.wx.order.dto.WeChatApiResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@Slf4j
+@Service
+public class ShippingService {
+
+
+    private final WeChatApiConfig weChatApiConfig;
+
+    public ShippingService(WeChatApiConfig weChatApiConfig) {
+        this.weChatApiConfig = weChatApiConfig;
+    }
+
+    /**
+     * 调用微信 API 上传发货信息 (使用 Hutool HttpUtil)
+     * @param request 发货信息请求体
+     * @return 微信 API 的响应
+     */
+    public WeChatApiResponse uploadShippingInfo(UploadShippingInfoRequest request) {
+        WeChatAuthService weChatAuthService = WeChatAuthFactory.getWeChatAuthService(request.getAppid());
+        String accessToken = weChatAuthService.getAccessToken(false);
+        if (accessToken == null) {
+            log.error("获取微信 Access Token 失败");
+            WeChatApiResponse errorResponse = new WeChatApiResponse();
+            errorResponse.setErrcode(-3); // 自定义错误码,表示获取Token失败
+            errorResponse.setErrmsg("获取微信 Access Token 失败");
+            return errorResponse;
+        }
+
+        String url = UriComponentsBuilder.fromHttpUrl(weChatApiConfig.getUploadShippingInfoUrl())
+                .queryParam("access_token", accessToken)
+                .toUriString();
+
+        log.debug("请求微信上传发货接口 Appid: {} URL: {}", request.getAppid(),url);
+
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        String requestBodyJson = null;
+        try {
+            requestBodyJson = objectMapper.writeValueAsString(request);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+        log.debug("请求体 JSON: {}", requestBodyJson);
+
+
+        HttpResponse httpResponse = null;
+        try {
+            HttpRequest httpRequest = HttpUtil.createPost(url)
+                    .header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
+                    .body(requestBodyJson)
+                    .timeout(10000);
+
+            httpResponse = httpRequest.execute();
+
+            int statusCode = httpResponse.getStatus();
+            String responseBodyString = httpResponse.body();
+
+            log.info("微信接口响应状态: {}", statusCode);
+            log.info("微信接口响应体: {}", responseBodyString);
+
+            if (httpResponse.isOk()) {
+                WeChatApiResponse weChatApiResponse = JSONUtil.toBean(responseBodyString, WeChatApiResponse.class);
+
+                if (!weChatApiResponse.isSuccess()) {
+                    log.warn("微信接口返回业务错误: code={}, message={}", weChatApiResponse.getErrcode(), weChatApiResponse.getErrmsg());
+                    if(ObjectUtil.equal(weChatApiResponse.getErrcode(),40001)) {
+                        log.info("token缓存失效,清除token,等待下次执行...");
+                        weChatAuthService.clearToken();
+                    }
+                }
+                return weChatApiResponse;
+
+            } else {
+                log.error("调用微信接口收到非成功状态码: {}", statusCode);
+                try {
+                    WeChatApiResponse errorResponse = JSONUtil.toBean(responseBodyString, WeChatApiResponse.class);
+                    if (errorResponse.getErrcode() == null) {
+                        errorResponse.setErrcode(statusCode);
+                        errorResponse.setErrmsg("微信接口HTTP错误,状态码: " + statusCode + ", 响应体: " + responseBodyString);
+                    }
+                    return errorResponse;
+                } catch (Exception parseEx) {
+                    log.warn("无法将微信错误响应体解析为JSON: {}", responseBodyString, parseEx);
+                    WeChatApiResponse errorResponse = new WeChatApiResponse();
+                    errorResponse.setErrcode(statusCode);
+                    errorResponse.setErrmsg("调用微信接口失败,状态码: " + statusCode + ", 原始响应体: " + responseBodyString);
+                    return errorResponse;
+                }
+            }
+        } catch (HttpException e) {
+            log.error("调用微信接口发生HTTP异常: {}", e.getMessage(), e);
+            WeChatApiResponse errorResponse = new WeChatApiResponse();
+            errorResponse.setErrcode(-1);
+            String detailedMessage = ExceptionUtil.getMessage(e);
+            errorResponse.setErrmsg("调用微信接口时发生HTTP异常: " + detailedMessage);
+            if (httpResponse != null) {
+                errorResponse.setErrmsg(errorResponse.getErrmsg() + ", HTTP状态码: " + httpResponse.getStatus());
+            }
+            return errorResponse;
+        } catch (Exception e) {
+            log.error("调用微信接口时发生意外错误", e);
+            WeChatApiResponse errorResponse = new WeChatApiResponse();
+            errorResponse.setErrcode(-2);
+            errorResponse.setErrmsg("调用微信接口时发生内部服务器错误: " + e.getMessage());
+            return errorResponse;
+        }finally {
+            if(httpResponse != null) {
+                httpResponse.close();
+            }
+        }
+    }
+}

+ 46 - 0
fs-service/src/main/java/com/fs/wx/order/service/WeChatAuthFactory.java

@@ -0,0 +1,46 @@
+package com.fs.wx.order.service;
+
+import com.fs.pay.domain.PaymentMiniProgramConfig;
+import com.fs.pay.mapper.PaymentMiniProgramConfigMapper;
+import com.fs.wx.order.service.impl.InMemoryWeChatAuthServiceImpl;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class WeChatAuthFactory implements ApplicationContextAware, SmartInitializingSingleton {
+    private static ApplicationContext applicationContext;
+    private final static Map<String,WeChatAuthService> weChatAuthServices = new ConcurrentHashMap<>();
+
+    @Override
+    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
+        WeChatAuthFactory.applicationContext = applicationContext;
+    }
+
+    @Override
+    public void afterSingletonsInstantiated() {
+        PaymentMiniProgramConfigMapper mapper = applicationContext.getBean(PaymentMiniProgramConfigMapper.class);
+        List<PaymentMiniProgramConfig> configs = mapper.selectAll();
+        for (PaymentMiniProgramConfig config : configs) {
+            WeChatAuthService weChatAuthService = new InMemoryWeChatAuthServiceImpl(config.getAppid(),config.getAppSecret());
+            weChatAuthServices.put(config.getAppid(),weChatAuthService);
+        }
+    }
+
+    /**
+     * 通过appid获取WeChatAuthService
+     * @param appid 小程序ID
+     * @return WeChatAuthService
+     */
+    public static WeChatAuthService getWeChatAuthService(String appid){
+        return weChatAuthServices.get(appid);
+    }
+
+}

+ 22 - 0
fs-service/src/main/java/com/fs/wx/order/service/WeChatAuthService.java

@@ -0,0 +1,22 @@
+package com.fs.wx.order.service;
+
+public interface WeChatAuthService {
+    void clearToken();
+
+    /**
+     * 获取有效的微信小程序 Access Token
+     * @param forceRefresh 是否强制刷新,忽略缓存
+     * @return Access Token
+     * @throws RuntimeException 如果获取失败
+     */
+    String getAccessToken(boolean forceRefresh);
+
+    /**
+     * 获取有效的微信小程序 Access Token (优先使用缓存)
+     * @return Access Token
+     * @throws RuntimeException 如果获取失败
+     */
+    default String getAccessToken() {
+        return getAccessToken(true);
+    }
+}

+ 114 - 0
fs-service/src/main/java/com/fs/wx/order/service/impl/InMemoryWeChatAuthServiceImpl.java

@@ -0,0 +1,114 @@
+package com.fs.wx.order.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.fs.wx.order.service.WeChatAuthService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InMemoryWeChatAuthServiceImpl implements WeChatAuthService {
+
+    private static final Logger log = LoggerFactory.getLogger(InMemoryWeChatAuthServiceImpl.class);
+    private String cachedToken = null;
+    private long expiryTime = 0; // Token 过期时间戳 (ms)
+
+    private final String appId;
+    private final String appSecret;
+
+    private final String tokenUrlFormat = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
+
+
+    public InMemoryWeChatAuthServiceImpl(String appId, String appSecret) {
+        this.appId = appId;
+        this.appSecret = appSecret;
+    }
+
+    @Override
+    public synchronized void clearToken(){
+        log.info("清除token缓存...");
+        cachedToken = null;
+        expiryTime = 0;
+    }
+
+    @Override
+    public synchronized String getAccessToken(boolean forceRefresh) {
+        long now = System.currentTimeMillis();
+        if (!forceRefresh && cachedToken != null && now < expiryTime) {
+            log.debug("Using cached access token.");
+            return cachedToken;
+        }
+
+        if (StrUtil.hasBlank(appId, appSecret)) {
+            log.error("appId或者appSecret不存在!");
+            throw new RuntimeException("appId或者appSecret不存在!");
+        }
+
+
+        String url = String.format(tokenUrlFormat, appId, appSecret);
+        log.info(url);
+        HttpResponse httpResponse = null;
+        String body = null;
+
+        try {
+            httpResponse = HttpRequest.get(url)
+                    .timeout(5000)
+                    .execute();
+
+            body = httpResponse.body();
+
+            if (!httpResponse.isOk()) {
+                log.error("获取accessToken失败!. Status: {}, Body: {}", httpResponse.getStatus(), body);
+                String errorMsg = parseErrorMsg(body);
+                throw new RuntimeException("获取accessToken失败! Status: " + httpResponse.getStatus() + ", Message: " + errorMsg);
+            }
+
+            if (StrUtil.isBlank(body)) {
+                log.error("获取accessToken失败!.  URL: {}", url);
+                throw new RuntimeException("获取accessToken失败!");
+            }
+
+            JSONObject responseJson = JSONUtil.parseObj(body);
+
+            if (responseJson.containsKey("access_token")) {
+                cachedToken = responseJson.getStr("access_token");
+                if(StrUtil.isBlank(cachedToken)){
+                    log.error("获取accessToken失败!response: {}", body);
+                    throw new RuntimeException("获取accessToken失败!");
+                }
+
+                long expiresInSeconds = responseJson.getLong("expires_in", 7200L);
+                expiryTime = now + (expiresInSeconds - 120) * 1000;
+                log.info("获取accessToken获取成功 {}",cachedToken);
+                return cachedToken;
+            } else {
+                String errorMsg = responseJson.getStr("errmsg", "Unknown error: access_token missing in response");
+                log.error("Failed to fetch access token, 'access_token' key missing. Response: {}", body);
+                throw new RuntimeException("Failed to fetch access token: " + errorMsg);
+            }
+        } catch (Exception e) {
+            cachedToken = null;
+            expiryTime = 0;
+            log.error("Error fetching access token from URL: {}", url, e);
+            throw new RuntimeException("Error fetching access token: " + e.getMessage(), e);
+        }finally {
+            if(httpResponse != null) {
+                httpResponse.close();
+            }
+        }
+    }
+
+    private String parseErrorMsg(String jsonBody) {
+        if (StrUtil.isBlank(jsonBody)) {
+            return "返回为空!";
+        }
+        try {
+            JSONObject json = JSONUtil.parseObj(jsonBody);
+            return json.getStr("errmsg", "Unknown error in response body");
+        } catch (Exception e) {
+            return "返回解析失败!";
+        }
+    }
+}

+ 107 - 0
fs-service/src/main/java/com/fs/wx/order/service/impl/LiveExpressToWxService.java

@@ -0,0 +1,107 @@
+package com.fs.wx.order.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.domain.FsStorePayment;
+import com.fs.his.mapper.FsStoreOrderMapper;
+import com.fs.his.mapper.FsStorePaymentMapper;
+import com.fs.live.domain.LiveOrder;
+import com.fs.live.domain.LiveOrderPayment;
+import com.fs.live.mapper.LiveOrderMapper;
+import com.fs.live.mapper.LiveOrderPaymentMapper;
+import com.fs.live.service.ILiveOrderService;
+import com.fs.wx.order.service.ExpressToWxService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE;
+
+@Service
+@Scope(value = SCOPE_PROTOTYPE)
+public class LiveExpressToWxService extends ExpressToWxService {
+    @Autowired
+    private LiveOrderMapper liveOrderMapper;
+    @Autowired
+    private LiveOrderPaymentMapper liveOrderPaymentMapper;
+
+    private LiveOrder liveOrder;
+    private LiveOrderPayment liveOrderPayment ;
+
+    @Override
+    public void setOrderCode(String orderCode) {
+        super.setOrderCode(orderCode);
+        this.liveOrder = liveOrderMapper.selectLiveOrderByOrderCode(getOrderCode());
+        if(ObjectUtil.isNull(liveOrder)) {
+            throw new IllegalArgumentException(String.format("该订单 %s 未找到!",getOrderCode()));
+        }
+        LiveOrderPayment fsStorePayments = liveOrderPaymentMapper.selectLiveOrderLatestPayByOrderId(liveOrder.getOrderId());
+        if(fsStorePayments == null){
+            throw new IllegalArgumentException(String.format("该订单 %s 未找到对应支付记录!", getOrderCode()));
+        }
+    }
+
+
+    @Override
+    public String getTransactionId() {
+        return liveOrderPayment.getBankTransactionId();
+    }
+
+    @Override
+    public String getUserPhone() {
+        return liveOrder.getUserPhone();
+    }
+
+    @Override
+    public String getOrderGoodsInfo() {
+        return getOrderGoodsInfo(liveOrder);
+    }
+
+    @Override
+    public String getExpressCompany() {
+
+        return liveOrder.getDeliveryCode();
+    }
+
+    @Override
+    public String getExpressNo() {
+
+        return liveOrder.getDeliverySn();
+    }
+
+    @Override
+    public boolean support(Integer type) {
+        return 1 == type;
+    }
+
+
+    /**
+     * 获取订单商品信息
+     * @return
+     */
+    private String getOrderGoodsInfo(LiveOrder order){
+        StringBuilder title = new StringBuilder();
+        // 如果是套餐
+//        if(ObjectUtil.equal(order.getIsPackage(),1)){
+//            String packageJson = order.getPackageJson();
+//            JSONObject jsonObject = JSON.parseObject(packageJson);
+//            title = new StringBuilder(jsonObject.getString("title"));
+//        } else {
+            String itemJson = order.getItemJson();
+            com.alibaba.fastjson.JSONArray arrays = JSON.parseArray(itemJson);
+            for(int i=0;i<arrays.size();i++){
+                JSONObject jsonObject = arrays.getJSONObject(i);
+                String jsonInfo = jsonObject.getString("jsonInfo");
+                JSONObject jsonObject1 = JSON.parseObject(jsonInfo);
+                String productName = jsonObject1.getString("productName");
+                title.append(productName).append("\n");
+            }
+//        }
+        return title.toString();
+    }
+}

+ 107 - 0
fs-service/src/main/java/com/fs/wx/order/service/impl/ShopExpressToWxService.java

@@ -0,0 +1,107 @@
+package com.fs.wx.order.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.domain.FsStorePayment;
+import com.fs.his.mapper.FsStoreOrderMapper;
+import com.fs.his.mapper.FsStorePaymentMapper;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStorePaymentScrm;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
+import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
+import com.fs.wx.order.service.ExpressToWxService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE;
+
+@Service
+@Scope(value = SCOPE_PROTOTYPE)
+public class ShopExpressToWxService extends ExpressToWxService {
+    @Autowired
+    private FsStoreOrderScrmMapper fsStoreOrderMapper;
+    @Autowired
+    private FsStorePaymentScrmMapper fsStorePaymentMapper;
+
+    private FsStoreOrderScrm fsStoreOrder;
+    private FsStorePaymentScrm fsStorePayment;
+
+    @Override
+    public void setOrderCode(String orderCode) {
+        super.setOrderCode(orderCode);
+        this.fsStoreOrder = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(getOrderCode());
+        if(ObjectUtil.isNull(fsStoreOrder)) {
+            throw new IllegalArgumentException(String.format("该订单 %s 未找到!",getOrderCode()));
+        }
+        List<FsStorePaymentScrm> fsStorePayments = fsStorePaymentMapper.selectFsStorePaymentByOrderId(fsStoreOrder.getId());
+        if(CollectionUtils.isEmpty(fsStorePayments)){
+            throw new IllegalArgumentException(String.format("该订单 %s 未找到对应支付记录!", getOrderCode()));
+        }
+        fsStorePayment = fsStorePayments.get(0);
+    }
+
+
+    @Override
+    public String getTransactionId() {
+        return fsStorePayment.getBankTransactionId();
+    }
+
+    @Override
+    public String getUserPhone() {
+        return fsStoreOrder.getUserPhone();
+    }
+
+    @Override
+    public String getOrderGoodsInfo() {
+        return getOrderGoodsInfo(fsStoreOrder);
+    }
+
+    @Override
+    public String getExpressCompany() {
+
+        return fsStoreOrder.getDeliveryCode();
+    }
+
+    @Override
+    public String getExpressNo() {
+
+        return fsStoreOrder.getDeliverySn();
+    }
+
+    @Override
+    public boolean support(Integer type) {
+        return 0 == type;
+    }
+
+
+    /**
+     * 获取订单商品信息
+     * @return
+     */
+    private String getOrderGoodsInfo(FsStoreOrderScrm order){
+        StringBuilder title = new StringBuilder();
+        // 如果是套餐
+//        if(ObjectUtil.equal(order.getIsPackage(),1)){
+//            String packageJson = order.getPackageJson();
+//            JSONObject jsonObject = JSON.parseObject(packageJson);
+//            title = new StringBuilder(jsonObject.getString("title"));
+//        } else {
+            String itemJson = order.getItemJson();
+            com.alibaba.fastjson.JSONArray arrays = JSON.parseArray(itemJson);
+            for(int i=0;i<arrays.size();i++){
+                JSONObject jsonObject = arrays.getJSONObject(i);
+                String jsonInfo = jsonObject.getString("jsonInfo");
+                JSONObject jsonObject1 = JSON.parseObject(jsonInfo);
+                String productName = jsonObject1.getString("productName");
+                title.append(productName).append("\n");
+            }
+//        }
+        return title.toString();
+    }
+}

+ 5 - 0
fs-service/src/main/resources/application-common.yml

@@ -138,3 +138,8 @@ image:
   storage:
     local-path: C:\logoFile\logo.jpg
     server-path: C:\logoFile\logo.jpg
+# application.properties
+wechat:
+  api:
+    base-url: https://api.weixin.qq.com
+    upload-shipping-info: /wxa/sec/order/upload_shipping_info

+ 2 - 2
fs-service/src/main/resources/application-druid-jzzx.yml

@@ -175,6 +175,6 @@ openIM:
 im:
     type: NONE
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: true
+isNewWxMerchant: false
 
-enableRedPackAccount: 0
+enableRedPackAccount: 1

+ 5 - 0
fs-service/src/main/resources/mapper/course/FsUserCoursePeriodDaysMapper.xml

@@ -243,4 +243,9 @@
     <select id="selectFsUserCoursePeriodDaysForLastById" resultType="java.lang.Long">
        select id from fs_user_course_period_days where del_flag ='0' and period_id = #{periodId} and lesson &gt;= #{lesson} order by lesson
     </select>
+    <select id="selectFsUserCoursePeriodDaysByCourseId"
+            resultType="com.fs.course.domain.FsUserCoursePeriodDays">
+        <include refid="selectFsUserCoursePeriodDaysVo"/>
+                 where del_flag ='0' and course_id = #{courseId}
+    </select>
 </mapper>

+ 1 - 0
fs-service/src/main/resources/mapper/live/LiveAutoTaskMapper.xml

@@ -33,6 +33,7 @@
             <if test="triggerType != null "> and trigger_type = #{triggerType}</if>
             <if test="triggerValue != null"> and trigger_value = #{triggerValue}</if>
             <if test="absValue != null"> and abs_value = #{absValue}</if>
+            <if test="taskType != null"> and task_type = #{taskType}</if>
             <if test="content != null  and content != ''"> and content = #{content}</if>
             <if test="status != null "> and status = #{status}</if>
             <if test="createTime != null "> and create_time = #{createTime}</if>

+ 195 - 0
fs-service/src/main/resources/mapper/pay/PaymentMiniProgramConfigMapper.xml

@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.pay.mapper.PaymentMiniProgramConfigMapper">
+
+    <resultMap type="PaymentMiniProgramConfig" id="PaymentMiniProgramConfigResult">
+        <result property="id"    column="id"    />
+        <result property="payType"    column="pay_type"    />
+        <result property="appName"    column="appname"    />
+        <result property="appid"    column="appid"    />
+        <result property="appSecret"    column="appsecret"    />
+        <result property="ybMerchantNo"    column="yb_merchant_no"    />
+        <result property="ybKey"    column="yb_key"    />
+        <result property="ybNotifyUrl"    column="yb_notify_url"    />
+        <result property="tzhMerchantNo"    column="tzh_merchant_no"    />
+        <result property="tzhAppsecret"    column="tzh_appsecret"    />
+        <result property="tzhPrivateKey"    column="tzh_private_key"    />
+        <result property="tzhPublicKey"    column="tzh_public_key"    />
+        <result property="tzhAppkey"    column="tzh_appkey"    />
+        <result property="tzhPayNotifyUrl"    column="tzh_pay_notify_url"    />
+        <result property="tzhRefundNotifyUrl"    column="tzh_refund_notify_url"    />
+        <result property="tzhSplitNotifyUrl"    column="tzh_split_notify_url"    />
+        <result property="wxMerchantNo"    column="wx_merchant_no"    />
+        <result property="wxKey"    column="wx_key"    />
+        <result property="hfProductNo"    column="hf_product_no"    />
+        <result property="hfSystemNo"    column="hf_system_no"    />
+        <result property="hfMerchantNo"    column="hf_merchant_no"    />
+        <result property="hfPrivateKey"    column="hf_private_key"    />
+        <result property="hfPublicKey"    column="hf_public_key"    />
+        <result property="hfPayNotifyUrl"    column="hf_pay_notify_url"    />
+        <result property="hfLargePayNotifyUrl"    column="hf_large_pay_notify_url"    />
+        <result property="hfRefundNotifyUrl"    column="hf_refund_notify_url"    />
+        <result property="hfLargeRefundNotifyUrl"    column="hf_large_refund_notify_url"    />
+        <result property="status"    column="status"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectPaymentMiniProgramConfigVo">
+        select id, pay_type, appid,appname,appsecret, yb_merchant_no, yb_key, yb_notify_url, tzh_merchant_no, tzh_appsecret, tzh_private_key, tzh_public_key, tzh_appkey, tzh_pay_notify_url, tzh_refund_notify_url, tzh_split_notify_url, wx_merchant_no, wx_key, hf_product_no, hf_system_no, hf_merchant_no, hf_private_key, hf_public_key, hf_pay_notify_url, hf_large_pay_notify_url, hf_refund_notify_url, hf_large_refund_notify_url, status, create_time, update_time from payment_mini_program_config
+    </sql>
+
+    <select id="selectPaymentMiniProgramConfigList" parameterType="PaymentMiniProgramConfig" resultMap="PaymentMiniProgramConfigResult">
+        <include refid="selectPaymentMiniProgramConfigVo"/>
+        <where>
+            <if test="payType != null  and payType != ''"> and pay_type = #{payType}</if>
+            <if test="appid != null  and appid != ''"> and appid = #{appid}</if>
+            <if test="ybMerchantNo != null  and ybMerchantNo != ''"> and yb_merchant_no = #{ybMerchantNo}</if>
+            <if test="ybKey != null  and ybKey != ''"> and yb_key = #{ybKey}</if>
+            <if test="ybNotifyUrl != null  and ybNotifyUrl != ''"> and yb_notify_url = #{ybNotifyUrl}</if>
+            <if test="tzhMerchantNo != null  and tzhMerchantNo != ''"> and tzh_merchant_no = #{tzhMerchantNo}</if>
+            <if test="tzhAppsecret != null  and tzhAppsecret != ''"> and tzh_appsecret = #{tzhAppsecret}</if>
+            <if test="tzhPrivateKey != null  and tzhPrivateKey != ''"> and tzh_private_key = #{tzhPrivateKey}</if>
+            <if test="tzhPublicKey != null  and tzhPublicKey != ''"> and tzh_public_key = #{tzhPublicKey}</if>
+            <if test="tzhAppkey != null  and tzhAppkey != ''"> and tzh_appkey = #{tzhAppkey}</if>
+            <if test="tzhPayNotifyUrl != null  and tzhPayNotifyUrl != ''"> and tzh_pay_notify_url = #{tzhPayNotifyUrl}</if>
+            <if test="tzhRefundNotifyUrl != null  and tzhRefundNotifyUrl != ''"> and tzh_refund_notify_url = #{tzhRefundNotifyUrl}</if>
+            <if test="tzhSplitNotifyUrl != null  and tzhSplitNotifyUrl != ''"> and tzh_split_notify_url = #{tzhSplitNotifyUrl}</if>
+            <if test="wxMerchantNo != null  and wxMerchantNo != ''"> and wx_merchant_no = #{wxMerchantNo}</if>
+            <if test="wxKey != null  and wxKey != ''"> and wx_key = #{wxKey}</if>
+            <if test="hfProductNo != null  and hfProductNo != ''"> and hf_product_no = #{hfProductNo}</if>
+            <if test="hfSystemNo != null  and hfSystemNo != ''"> and hf_system_no = #{hfSystemNo}</if>
+            <if test="hfMerchantNo != null  and hfMerchantNo != ''"> and hf_merchant_no = #{hfMerchantNo}</if>
+            <if test="hfPrivateKey != null  and hfPrivateKey != ''"> and hf_private_key = #{hfPrivateKey}</if>
+            <if test="hfPublicKey != null  and hfPublicKey != ''"> and hf_public_key = #{hfPublicKey}</if>
+            <if test="hfPayNotifyUrl != null  and hfPayNotifyUrl != ''"> and hf_pay_notify_url = #{hfPayNotifyUrl}</if>
+            <if test="hfLargePayNotifyUrl != null  and hfLargePayNotifyUrl != ''"> and hf_large_pay_notify_url = #{hfLargePayNotifyUrl}</if>
+            <if test="hfRefundNotifyUrl != null  and hfRefundNotifyUrl != ''"> and hf_refund_notify_url = #{hfRefundNotifyUrl}</if>
+            <if test="hfLargeRefundNotifyUrl != null  and hfLargeRefundNotifyUrl != ''"> and hf_large_refund_notify_url = #{hfLargeRefundNotifyUrl}</if>
+            <if test="status != null "> and status = #{status}</if>
+        </where>
+    </select>
+
+    <select id="selectPaymentMiniProgramConfigById" parameterType="String" resultMap="PaymentMiniProgramConfigResult">
+        <include refid="selectPaymentMiniProgramConfigVo"/>
+        where id = #{id}
+    </select>
+    <select id="selectPaymentConfigByAppId" resultType="com.fs.pay.domain.PaymentMiniProgramConfig">
+        select * from payment_mini_program_config where appid=#{appid} limit 1
+    </select>
+
+    <insert id="insertPaymentMiniProgramConfig" parameterType="PaymentMiniProgramConfig" useGeneratedKeys="true" keyProperty="id">
+        insert into payment_mini_program_config
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="payType != null and payType != ''">pay_type,</if>
+            <if test="appName != null and appName != ''">appname,</if>
+            <if test="appid != null and appid != ''">appid,</if>
+            <if test="appSecret != null and appSecret != ''">appsecret,</if>
+            <if test="ybMerchantNo != null">yb_merchant_no,</if>
+            <if test="ybKey != null">yb_key,</if>
+            <if test="ybNotifyUrl != null">yb_notify_url,</if>
+            <if test="tzhMerchantNo != null">tzh_merchant_no,</if>
+            <if test="tzhAppsecret != null">tzh_appsecret,</if>
+            <if test="tzhPrivateKey != null">tzh_private_key,</if>
+            <if test="tzhPublicKey != null">tzh_public_key,</if>
+            <if test="tzhAppkey != null">tzh_appkey,</if>
+            <if test="tzhPayNotifyUrl != null">tzh_pay_notify_url,</if>
+            <if test="tzhRefundNotifyUrl != null">tzh_refund_notify_url,</if>
+            <if test="tzhSplitNotifyUrl != null">tzh_split_notify_url,</if>
+            <if test="wxMerchantNo != null">wx_merchant_no,</if>
+            <if test="wxKey != null">wx_key,</if>
+            <if test="hfProductNo != null">hf_product_no,</if>
+            <if test="hfSystemNo != null">hf_system_no,</if>
+            <if test="hfMerchantNo != null">hf_merchant_no,</if>
+            <if test="hfPrivateKey != null">hf_private_key,</if>
+            <if test="hfPublicKey != null">hf_public_key,</if>
+            <if test="hfPayNotifyUrl != null">hf_pay_notify_url,</if>
+            <if test="hfLargePayNotifyUrl != null">hf_large_pay_notify_url,</if>
+            <if test="hfRefundNotifyUrl != null">hf_refund_notify_url,</if>
+            <if test="hfLargeRefundNotifyUrl != null">hf_large_refund_notify_url,</if>
+            <if test="status != null">status,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="payType != null and payType != ''">#{payType},</if>
+            <if test="appName != null and appName != ''">#{appName},</if>
+            <if test="appid != null and appid != ''">#{appid},</if>
+            <if test="appSecret != null and appSecret != ''">#{appSecret},</if>
+            <if test="ybMerchantNo != null">#{ybMerchantNo},</if>
+            <if test="ybKey != null">#{ybKey},</if>
+            <if test="ybNotifyUrl != null">#{ybNotifyUrl},</if>
+            <if test="tzhMerchantNo != null">#{tzhMerchantNo},</if>
+            <if test="tzhAppsecret != null">#{tzhAppsecret},</if>
+            <if test="tzhPrivateKey != null">#{tzhPrivateKey},</if>
+            <if test="tzhPublicKey != null">#{tzhPublicKey},</if>
+            <if test="tzhAppkey != null">#{tzhAppkey},</if>
+            <if test="tzhPayNotifyUrl != null">#{tzhPayNotifyUrl},</if>
+            <if test="tzhRefundNotifyUrl != null">#{tzhRefundNotifyUrl},</if>
+            <if test="tzhSplitNotifyUrl != null">#{tzhSplitNotifyUrl},</if>
+            <if test="wxMerchantNo != null">#{wxMerchantNo},</if>
+            <if test="wxKey != null">#{wxKey},</if>
+            <if test="hfProductNo != null">#{hfProductNo},</if>
+            <if test="hfSystemNo != null">#{hfSystemNo},</if>
+            <if test="hfMerchantNo != null">#{hfMerchantNo},</if>
+            <if test="hfPrivateKey != null">#{hfPrivateKey},</if>
+            <if test="hfPublicKey != null">#{hfPublicKey},</if>
+            <if test="hfPayNotifyUrl != null">#{hfPayNotifyUrl},</if>
+            <if test="hfLargePayNotifyUrl != null">#{hfLargePayNotifyUrl},</if>
+            <if test="hfRefundNotifyUrl != null">#{hfRefundNotifyUrl},</if>
+            <if test="hfLargeRefundNotifyUrl != null">#{hfLargeRefundNotifyUrl},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updatePaymentMiniProgramConfig" parameterType="PaymentMiniProgramConfig">
+        update payment_mini_program_config
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="payType != null and payType != ''">pay_type = #{payType},</if>
+            <if test="appName != null and appName != ''">appname = #{appName},</if>
+            <if test="appid != null and appid != ''">appid = #{appid},</if>
+            <if test="appSecret != null and appSecret != ''">appsecret = #{appSecret},</if>
+            <if test="ybMerchantNo != null">yb_merchant_no = #{ybMerchantNo},</if>
+            <if test="ybKey != null">yb_key = #{ybKey},</if>
+            <if test="ybNotifyUrl != null">yb_notify_url = #{ybNotifyUrl},</if>
+            <if test="tzhMerchantNo != null">tzh_merchant_no = #{tzhMerchantNo},</if>
+            <if test="tzhAppsecret != null">tzh_appsecret = #{tzhAppsecret},</if>
+            <if test="tzhPrivateKey != null">tzh_private_key = #{tzhPrivateKey},</if>
+            <if test="tzhPublicKey != null">tzh_public_key = #{tzhPublicKey},</if>
+            <if test="tzhAppkey != null">tzh_appkey = #{tzhAppkey},</if>
+            <if test="tzhPayNotifyUrl != null">tzh_pay_notify_url = #{tzhPayNotifyUrl},</if>
+            <if test="tzhRefundNotifyUrl != null">tzh_refund_notify_url = #{tzhRefundNotifyUrl},</if>
+            <if test="tzhSplitNotifyUrl != null">tzh_split_notify_url = #{tzhSplitNotifyUrl},</if>
+            <if test="wxMerchantNo != null">wx_merchant_no = #{wxMerchantNo},</if>
+            <if test="wxKey != null">wx_key = #{wxKey},</if>
+            <if test="hfProductNo != null">hf_product_no = #{hfProductNo},</if>
+            <if test="hfSystemNo != null">hf_system_no = #{hfSystemNo},</if>
+            <if test="hfMerchantNo != null">hf_merchant_no = #{hfMerchantNo},</if>
+            <if test="hfPrivateKey != null">hf_private_key = #{hfPrivateKey},</if>
+            <if test="hfPublicKey != null">hf_public_key = #{hfPublicKey},</if>
+            <if test="hfPayNotifyUrl != null">hf_pay_notify_url = #{hfPayNotifyUrl},</if>
+            <if test="hfLargePayNotifyUrl != null">hf_large_pay_notify_url = #{hfLargePayNotifyUrl},</if>
+            <if test="hfRefundNotifyUrl != null">hf_refund_notify_url = #{hfRefundNotifyUrl},</if>
+            <if test="hfLargeRefundNotifyUrl != null">hf_large_refund_notify_url = #{hfLargeRefundNotifyUrl},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deletePaymentMiniProgramConfigById" parameterType="String">
+        delete from payment_mini_program_config where id = #{id}
+    </delete>
+
+    <delete id="deletePaymentMiniProgramConfigByIds" parameterType="String">
+        delete from payment_mini_program_config where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>