Przeglądaj źródła

feat:【MALL 商城】增加微信物流的对接(和社区同学,一起测试中。。。)

YunaiV 6 miesięcy temu
rodzic
commit
a34de9f223
15 zmienionych plików z 396 dodań i 5 usunięć
  1. 6 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java
  2. 13 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  3. 21 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
  4. 86 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeStatusSyncToWxaOrderHandler.java
  5. 15 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java
  6. 42 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java
  7. 2 1
      yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
  8. 16 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java
  9. 30 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaOrderNotifyConfirmReceiveReqDTO.java
  10. 67 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaOrderUploadShippingInfoReqDTO.java
  11. 2 1
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  12. 10 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java
  13. 18 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java
  14. 67 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
  15. 1 0
      yudao-server/src/main/resources/application.yaml

+ 6 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java

@@ -48,4 +48,10 @@ public class TradeOrderProperties {
     @NotNull(message = "评论超时时间不能为空")
     private Duration commentExpireTime;
 
+    /**
+     * 是否同步订单状态到微信小程序
+     */
+    @NotNull(message = "是否同步订单状态到微信小程序不能为空")
+    private Boolean statusSyncToWxaEnable;
+
 }

+ 13 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -401,6 +401,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null));
         // 4.2 发送订阅消息
         getSelf().sendDeliveryOrderMessage(order, deliveryReqVO);
+
+        // 5. 处理订单发货后逻辑
+        order.setLogisticsId(updateOrderObj.getLogisticsId()).setLogisticsNo(updateOrderObj.getLogisticsNo())
+                .setStatus(updateOrderObj.getStatus()).setDeliveryTime(updateOrderObj.getDeliveryTime());
+        tradeOrderHandlers.forEach(handler -> handler.afterDeliveryOrder(order));
     }
 
     @Async
@@ -499,15 +504,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
      * @param order 订单
      */
     private void receiveOrder0(TradeOrderDO order) {
-        // 更新 TradeOrderDO 状态为已完成
+        // 1. 更新 TradeOrderDO 状态为已完成
+        LocalDateTime receiveTime = LocalDateTime.now();
         int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
-                new TradeOrderDO().setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(LocalDateTime.now()));
+                new TradeOrderDO().setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(receiveTime));
         if (updateCount == 0) {
             throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
         }
 
-        // 插入订单日志
+        // 2. 插入订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());
+
+        // 3. 执行 TradeOrderHandler 后置处理
+        order.setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(receiveTime);
+        tradeOrderHandlers.forEach(handler -> handler.afterReceiveOrder(order));
     }
 
     /**

+ 21 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java

@@ -62,6 +62,27 @@ public interface TradeOrderHandler {
      */
     default void beforeDeliveryOrder(TradeOrderDO order) {}
 
+    /**
+     * 订单发货后
+     *
+     * @param order 订单
+     */
+    default void afterDeliveryOrder(TradeOrderDO order) {}
+
+    /**
+     * 订单收货前
+     *
+     * @param order 订单
+     */
+    default void beforeReceiveOrder(TradeOrderDO order) {}
+
+    /**
+     * 订单收货后
+     *
+     * @param order 订单
+     */
+    default void afterReceiveOrder(TradeOrderDO order) {}
+
     // ========== 公用方法 ==========
 
     /**

+ 86 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeStatusSyncToWxaOrderHandler.java

@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
+import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
+import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
+import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
+import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+/**
+ * 同步订单状态到微信小程序的 {@link TradeOrderHandler} 实现类
+ *
+ * 背景:电商类目的微信小程序需要上传发货信息,不然微信支付会被封 = =!
+ * 注意:微信小程序开发环境下的订单不能用来发货。只有小程序正式版才会有发货,所以体验版无法调通,提示订单不存在。注意别踩坑。
+ */
+@Slf4j
+@Component
+@ConditionalOnProperty(prefix = "yudao.trade.order", value = "status-sync-to-wxa-enable")
+public class TradeStatusSyncToWxaOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private PayOrderApi payOrderApi;
+    @Resource
+    private SocialClientApi socialClientApi;
+
+    @Resource
+    private DeliveryExpressService expressService;
+
+    @Override
+    public void afterDeliveryOrder(TradeOrderDO order) {
+        // 注意:只有微信小程序支付的订单,才需要同步
+        if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) {
+            return;
+        }
+        PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
+        SocialWxaOrderUploadShippingInfoReqDTO reqDTO = new SocialWxaOrderUploadShippingInfoReqDTO()
+                .setTransactionId(payOrder.getChannelOrderNo())
+                .setOpenid(payOrder.getChannelUserId())
+                .setItemDesc(payOrder.getSubject())
+                .setReceiverContact(order.getReceiverMobile());
+        if (DeliveryTypeEnum.EXPRESS.getType().equals(order.getDeliveryType()) && StrUtil.isNotEmpty(order.getLogisticsNo())) {
+            reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_EXPRESS)
+                    .setExpressCompany(expressService.getDeliveryExpress(order.getLogisticsId()).getCode())
+                    .setLogisticsNo(order.getLogisticsNo());
+        } else if (DeliveryTypeEnum.PICK_UP.getType().equals(order.getDeliveryType())) {
+            reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_PICK_UP);
+        } else {
+            reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_VIRTUAL);
+        }
+        try {
+            socialClientApi.uploadWxaOrderShippingInfo(UserTypeEnum.MEMBER.getValue(), reqDTO);
+        } catch (Exception ex) {
+            log.error("[afterDeliveryOrder][订单({}) 上传订单物流信息到微信小程序失败]", order, ex);
+        }
+    }
+
+    @Override
+    public void afterReceiveOrder(TradeOrderDO order) {
+        // 注意:只有微信小程序支付的订单,才需要同步
+        if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) {
+            return;
+        }
+        PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
+        SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO = new SocialWxaOrderNotifyConfirmReceiveReqDTO()
+                .setTransactionId(payOrder.getChannelOrderNo())
+                .setReceivedTime(order.getReceiveTime());
+        try {
+            socialClientApi.notifyWxaOrderConfirmReceive(UserTypeEnum.MEMBER.getValue(), reqDTO);
+        } catch (Exception ex) {
+            log.error("[afterReceiveOrder][订单({}) 通知订单收货到微信小程序失败]", order, ex);
+        }
+    }
+
+    // TODO @芋艿:【设置路径】 https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html#%E5%85%AD%E3%80%81%E6%B6%88%E6%81%AF%E8%B7%B3%E8%BD%AC%E8%B7%AF%E5%BE%84%E8%AE%BE%E7%BD%AE%E6%8E%A5%E5%8F%A3
+
+}

+ 15 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java

@@ -32,6 +32,10 @@ public class PayOrderRespDTO {
     private String merchantOrderId;
 
     // ========== 订单相关字段 ==========
+    /**
+     * 商品标题
+     */
+    private String subject;
     /**
      * 支付金额,单位:分
      */
@@ -50,4 +54,15 @@ public class PayOrderRespDTO {
 
     // ========== 渠道相关字段 ==========
 
+    /**
+     * 渠道用户编号
+     *
+     * 例如说,微信 openid、支付宝账号
+     */
+    private String channelUserId;
+    /**
+     * 渠道订单号
+     */
+    private String channelOrderNo;
+
 }

+ 42 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pay.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付渠道的编码的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PayChannelEnum {
+
+    WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号网页
+    WX_LITE("wx_lite", "微信小程序支付"),
+    WX_APP("wx_app", "微信 App 支付"),
+    WX_NATIVE("wx_native", "微信 Native 支付"),
+    WX_WAP("wx_wap", "微信 Wap 网站支付"), // H5 网页
+    WX_BAR("wx_bar", "微信付款码支付"),
+
+    ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付"),
+    ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付"),
+    ALIPAY_APP("alipay_app", "支付宝App 支付"),
+    ALIPAY_QR("alipay_qr", "支付宝扫码支付"),
+    ALIPAY_BAR("alipay_bar", "支付宝条码支付"),
+    MOCK("mock", "模拟支付"),
+
+    WALLET("wallet", "钱包支付");
+
+    /**
+     * 编码
+     *
+     * 参考 <a href="https://www.pingxx.com/api/支付渠道属性值.html">支付渠道属性值</a>
+     */
+    private final String code;
+    /**
+     * 名字
+     */
+    private final String name;
+
+}

+ 2 - 1
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java

@@ -179,7 +179,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
 
     private PayOrderRespDTO doParseOrderNotifyV3(String body, Map<String, String> headers) throws WxPayException {
         // 1. 解析回调
-        SignatureHeader signatureHeader = getRequestHeader(headers);
+//        SignatureHeader signatureHeader = getRequestHeader(headers);
+        SignatureHeader signatureHeader = null;
         WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, signatureHeader);
         WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult();
         // 2. 构建结果

+ 16 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java

@@ -65,4 +65,20 @@ public interface SocialClientApi {
      */
     void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO);
 
+    /**
+     * 上传订单发货到微信小程序
+     *
+     * @param userType 用户类型
+     * @param reqDTO 请求
+     */
+    void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO);
+
+    /**
+     * 通知订单收货到微信小程序
+     *
+     * @param userType 用户类型
+     * @param reqDTO 请求
+     */
+    void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO);
+
 }

+ 30 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaOrderNotifyConfirmReceiveReqDTO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.system.api.social.dto;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 小程序订单上传购物详情
+ *
+ * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/shopping-order/normal-shopping-detail/uploadShoppingInfo.html">上传购物详情</a>
+ * @author 芋道源码
+ */
+@Data
+public class SocialWxaOrderNotifyConfirmReceiveReqDTO {
+
+    /**
+     * 原支付交易对应的微信订单号
+     */
+    @NotEmpty(message = "原支付交易对应的微信订单号不能为空")
+    private String transactionId;
+
+    /**
+     * 快递签收时间
+     */
+    @NotNull(message = "快递签收时间不能为空")
+    private LocalDateTime receivedTime;
+
+}

+ 67 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaOrderUploadShippingInfoReqDTO.java

@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.system.api.social.dto;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 小程序订单上传购物详情
+ *
+ * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/shopping-order/normal-shopping-detail/uploadShoppingInfo.html">上传购物详情</a>
+ * @author 芋道源码
+ */
+@Data
+public class SocialWxaOrderUploadShippingInfoReqDTO {
+
+    /**
+     * 物流模式 - 实体物流配送采用快递公司进行实体物流配送形式
+     */
+    public static final Integer LOGISTICS_TYPE_EXPRESS = 1;
+    /**
+     * 物流模式 - 虚拟商品,虚拟商品,例如话费充值,点卡等,无实体配送形式
+     */
+    public static final Integer LOGISTICS_TYPE_VIRTUAL = 3;
+    /**
+     * 物流模式 - 用户自提
+     */
+    public static final Integer LOGISTICS_TYPE_PICK_UP = 4;
+
+    /**
+     * 支付者,支付者信息(openid)
+     */
+    @NotEmpty(message = "支付者,支付者信息(openid)不能为空")
+    private String openid;
+
+    /**
+     * 原支付交易对应的微信订单号
+     */
+    @NotEmpty(message = "原支付交易对应的微信订单号不能为空")
+    private String transactionId;
+
+    /**
+     * 物流模式
+     */
+    @NotNull(message = "物流模式不能为空")
+    private Integer logisticsType;
+    /**
+     * 物流发货单号
+     */
+    private String logisticsNo;
+    /**
+     * 物流公司编号
+     *
+     * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/express_search.html#%E8%8E%B7%E5%8F%96%E8%BF%90%E5%8A%9Bid%E5%88%97%E8%A1%A8get-delivery-list">物流查询插件简介</a>
+     */
+    private String expressCompany;
+    /**
+     * 商品信息
+     */
+    @NotEmpty(message = "商品信息不能为空")
+    private String itemDesc;
+    /**
+     * 收件人手机号
+     */
+    @NotEmpty(message = "收件人手机号")
+    private String receiverContact;
+
+}

+ 2 - 1
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -124,10 +124,11 @@ public interface ErrorCodeConstants {
     ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR = new ErrorCode(1_002_018_201, "获得小程序码失败");
     ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR = new ErrorCode(1_002_018_202, "获得小程序订阅消息模版失败");
     ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR = new ErrorCode(1_002_018_203, "发送小程序订阅消息失败");
+    ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR = new ErrorCode(1_002_018_204, "上传微信小程序发货信息失败");
+    ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_NOTIFY_CONFIRM_RECEIVE_ERROR = new ErrorCode(1_002_018_205, "上传微信小程序订单收货信息失败");
     ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_210, "社交客户端不存在");
     ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_211, "社交客户端已存在配置");
 
-
     // ========== OAuth2 客户端 1-002-020-000 =========
     ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在");
     ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1_002_020_001, "OAuth2 客户端编号已存在");

+ 10 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java

@@ -94,4 +94,14 @@ public class SocialClientApiImpl implements SocialClientApi {
         socialClientService.sendSubscribeMessage(reqDTO, template.getPriTmplId(), socialUser.getOpenid());
     }
 
+    @Override
+    public void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO) {
+        socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO);
+    }
+
+    @Override
+    public void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO) {
+        socialClientService.notifyWxaOrderConfirmReceive(userType, reqDTO);
+    }
+
 }

+ 18 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.system.service.social;
 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
@@ -92,6 +94,22 @@ public interface SocialClientService {
      */
     void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId);
 
+    /**
+     * 上传订单发货到微信小程序
+     *
+     * @param userType 用户类型
+     * @param reqDTO 请求
+     */
+    void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO);
+
+    /**
+     * 通知订单收货到微信小程序
+     *
+     * @param userType 用户类型
+     * @param reqDTO 请求
+     */
+    void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO);
+
     // =================== 客户端管理 ===================
 
     /**

+ 67 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java

@@ -5,11 +5,15 @@ import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
 import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
 import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
+import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*;
+import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
 import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;
 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.DesensitizedUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -19,6 +23,8 @@ import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
@@ -54,14 +60,17 @@ import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
 
 import java.time.Duration;
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
+import static cn.hutool.core.date.DatePattern.UTC_MS_WITH_XXX_OFFSET_PATTERN;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static java.util.Collections.singletonList;
 
 /**
  * 社交应用 Service 实现类
@@ -327,6 +336,64 @@ public class SocialClientServiceImpl implements SocialClientService {
         return subscribeMessage;
     }
 
+    @Override
+    public void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO) {
+        WxMaService service = getWxMaService(userType);
+        List<ShippingListBean> shippingList;
+        if (Objects.equals(reqDTO.getLogisticsType(), SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_EXPRESS)) {
+            shippingList = singletonList(ShippingListBean.builder()
+                    .trackingNo(reqDTO.getLogisticsNo())
+                    .expressCompany(reqDTO.getExpressCompany())
+                    .itemDesc(reqDTO.getItemDesc())
+                    .contact(ContactBean.builder().receiverContact(DesensitizedUtil.mobilePhone(reqDTO.getReceiverContact())).build())
+                    .build());
+        } else {
+            shippingList = singletonList(ShippingListBean.builder().itemDesc(reqDTO.getItemDesc()).build());
+        }
+        WxMaOrderShippingInfoUploadRequest request = WxMaOrderShippingInfoUploadRequest.builder()
+                .orderKey(OrderKeyBean.builder()
+                        .orderNumberType(2) // 使用原支付交易对应的微信订单号,即渠道单号
+                        .transactionId(reqDTO.getTransactionId())
+                        .build())
+                .logisticsType(reqDTO.getLogisticsType()) // 配送方式
+                .deliveryMode(1) // 统一发货
+                .shippingList(shippingList)
+                .payer(PayerBean.builder().openid(reqDTO.getOpenid()).build())
+                .uploadTime(LocalDateTimeUtil.format(LocalDateTime.now(), UTC_MS_WITH_XXX_OFFSET_PATTERN))
+                .build();
+        try {
+            WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().upload(request);
+            if (response.getErrCode() != 0) {
+                log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({}) response({})]", request, response);
+                throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, response.getErrMsg());
+            }
+            log.info("[uploadWxaOrderShippingInfo][上传微信小程序发货信息成功:request({}) response({})]", request, response);
+        } catch (WxErrorException ex) {
+            log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({})]", request, ex);
+            throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, ex.getError().getErrorMsg());
+        }
+    }
+
+    @Override
+    public void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO) {
+        WxMaService service = getWxMaService(userType);
+        WxMaOrderShippingInfoNotifyConfirmRequest request = WxMaOrderShippingInfoNotifyConfirmRequest.builder()
+                .transactionId(reqDTO.getTransactionId())
+                .receivedTime(LocalDateTimeUtil.toEpochMilli(reqDTO.getReceivedTime()))
+                .build();
+        try {
+            WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().notifyConfirmReceive(request);
+            if (response.getErrCode() != 0) {
+                log.error("[notifyWxaOrderConfirmReceive][确认收货提醒到微信小程序失败:request({}) response({})]", request, response);
+                throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_NOTIFY_CONFIRM_RECEIVE_ERROR, response.getErrMsg());
+            }
+            log.info("[notifyWxaOrderConfirmReceive][确认收货提醒到微信小程序成功:request({}) response({})]", request, response);
+        } catch (WxErrorException ex) {
+            log.error("[notifyWxaOrderConfirmReceive][确认收货提醒到微信小程序失败:request({})]", request, ex);
+            throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_NOTIFY_CONFIRM_RECEIVE_ERROR, ex.getError().getErrorMsg());
+        }
+    }
+
     /**
      * 获得 clientId + clientSecret 对应的 WxMpService 对象
      *

+ 1 - 0
yudao-server/src/main/resources/application.yaml

@@ -299,6 +299,7 @@ yudao:
       pay-expire-time: 2h # 支付的过期时间
       receive-expire-time: 14d # 收货的过期时间
       comment-expire-time: 7d # 评论的过期时间
+      status-sync-to-wxa-enable: true # 是否同步订单状态到微信小程序
     express:
       client: kd_100
       kd-niao: