Commit a34de9f2239cedfcba3ff189c76df36bb6c7b615

Authored by YunaiV
1 parent 9847d4cd

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

Showing 15 changed files with 396 additions and 5 deletions
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 { @@ -48,4 +48,10 @@ public class TradeOrderProperties {
48 @NotNull(message = "评论超时时间不能为空") 48 @NotNull(message = "评论超时时间不能为空")
49 private Duration commentExpireTime; 49 private Duration commentExpireTime;
50 50
  51 + /**
  52 + * 是否同步订单状态到微信小程序
  53 + */
  54 + @NotNull(message = "是否同步订单状态到微信小程序不能为空")
  55 + private Boolean statusSyncToWxaEnable;
  56 +
51 } 57 }
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 { @@ -401,6 +401,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
401 .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null)); 401 .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null));
402 // 4.2 发送订阅消息 402 // 4.2 发送订阅消息
403 getSelf().sendDeliveryOrderMessage(order, deliveryReqVO); 403 getSelf().sendDeliveryOrderMessage(order, deliveryReqVO);
  404 +
  405 + // 5. 处理订单发货后逻辑
  406 + order.setLogisticsId(updateOrderObj.getLogisticsId()).setLogisticsNo(updateOrderObj.getLogisticsNo())
  407 + .setStatus(updateOrderObj.getStatus()).setDeliveryTime(updateOrderObj.getDeliveryTime());
  408 + tradeOrderHandlers.forEach(handler -> handler.afterDeliveryOrder(order));
404 } 409 }
405 410
406 @Async 411 @Async
@@ -499,15 +504,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -499,15 +504,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
499 * @param order 订单 504 * @param order 订单
500 */ 505 */
501 private void receiveOrder0(TradeOrderDO order) { 506 private void receiveOrder0(TradeOrderDO order) {
502 - // 更新 TradeOrderDO 状态为已完成 507 + // 1. 更新 TradeOrderDO 状态为已完成
  508 + LocalDateTime receiveTime = LocalDateTime.now();
503 int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), 509 int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
504 - new TradeOrderDO().setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(LocalDateTime.now())); 510 + new TradeOrderDO().setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(receiveTime));
505 if (updateCount == 0) { 511 if (updateCount == 0) {
506 throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED); 512 throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
507 } 513 }
508 514
509 - // 插入订单日志 515 + // 2. 插入订单日志
510 TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus()); 516 TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());
  517 +
  518 + // 3. 执行 TradeOrderHandler 后置处理
  519 + order.setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(receiveTime);
  520 + tradeOrderHandlers.forEach(handler -> handler.afterReceiveOrder(order));
511 } 521 }
512 522
513 /** 523 /**
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 { @@ -62,6 +62,27 @@ public interface TradeOrderHandler {
62 */ 62 */
63 default void beforeDeliveryOrder(TradeOrderDO order) {} 63 default void beforeDeliveryOrder(TradeOrderDO order) {}
64 64
  65 + /**
  66 + * 订单发货后
  67 + *
  68 + * @param order 订单
  69 + */
  70 + default void afterDeliveryOrder(TradeOrderDO order) {}
  71 +
  72 + /**
  73 + * 订单收货前
  74 + *
  75 + * @param order 订单
  76 + */
  77 + default void beforeReceiveOrder(TradeOrderDO order) {}
  78 +
  79 + /**
  80 + * 订单收货后
  81 + *
  82 + * @param order 订单
  83 + */
  84 + default void afterReceiveOrder(TradeOrderDO order) {}
  85 +
65 // ========== 公用方法 ========== 86 // ========== 公用方法 ==========
66 87
67 /** 88 /**
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeStatusSyncToWxaOrderHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.order.handler;
  2 +
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  6 +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
  7 +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
  8 +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
  9 +import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
  10 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
  11 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
  12 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  13 +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
  14 +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
  15 +import jakarta.annotation.Resource;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  18 +import org.springframework.stereotype.Component;
  19 +
  20 +/**
  21 + * 同步订单状态到微信小程序的 {@link TradeOrderHandler} 实现类
  22 + *
  23 + * 背景:电商类目的微信小程序需要上传发货信息,不然微信支付会被封 = =!
  24 + * 注意:微信小程序开发环境下的订单不能用来发货。只有小程序正式版才会有发货,所以体验版无法调通,提示订单不存在。注意别踩坑。
  25 + */
  26 +@Slf4j
  27 +@Component
  28 +@ConditionalOnProperty(prefix = "yudao.trade.order", value = "status-sync-to-wxa-enable")
  29 +public class TradeStatusSyncToWxaOrderHandler implements TradeOrderHandler {
  30 +
  31 + @Resource
  32 + private PayOrderApi payOrderApi;
  33 + @Resource
  34 + private SocialClientApi socialClientApi;
  35 +
  36 + @Resource
  37 + private DeliveryExpressService expressService;
  38 +
  39 + @Override
  40 + public void afterDeliveryOrder(TradeOrderDO order) {
  41 + // 注意:只有微信小程序支付的订单,才需要同步
  42 + if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) {
  43 + return;
  44 + }
  45 + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
  46 + SocialWxaOrderUploadShippingInfoReqDTO reqDTO = new SocialWxaOrderUploadShippingInfoReqDTO()
  47 + .setTransactionId(payOrder.getChannelOrderNo())
  48 + .setOpenid(payOrder.getChannelUserId())
  49 + .setItemDesc(payOrder.getSubject())
  50 + .setReceiverContact(order.getReceiverMobile());
  51 + if (DeliveryTypeEnum.EXPRESS.getType().equals(order.getDeliveryType()) && StrUtil.isNotEmpty(order.getLogisticsNo())) {
  52 + reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_EXPRESS)
  53 + .setExpressCompany(expressService.getDeliveryExpress(order.getLogisticsId()).getCode())
  54 + .setLogisticsNo(order.getLogisticsNo());
  55 + } else if (DeliveryTypeEnum.PICK_UP.getType().equals(order.getDeliveryType())) {
  56 + reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_PICK_UP);
  57 + } else {
  58 + reqDTO.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_VIRTUAL);
  59 + }
  60 + try {
  61 + socialClientApi.uploadWxaOrderShippingInfo(UserTypeEnum.MEMBER.getValue(), reqDTO);
  62 + } catch (Exception ex) {
  63 + log.error("[afterDeliveryOrder][订单({}) 上传订单物流信息到微信小程序失败]", order, ex);
  64 + }
  65 + }
  66 +
  67 + @Override
  68 + public void afterReceiveOrder(TradeOrderDO order) {
  69 + // 注意:只有微信小程序支付的订单,才需要同步
  70 + if (ObjUtil.notEqual(order.getPayChannelCode(), PayChannelEnum.WX_LITE.getCode())) {
  71 + return;
  72 + }
  73 + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
  74 + SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO = new SocialWxaOrderNotifyConfirmReceiveReqDTO()
  75 + .setTransactionId(payOrder.getChannelOrderNo())
  76 + .setReceivedTime(order.getReceiveTime());
  77 + try {
  78 + socialClientApi.notifyWxaOrderConfirmReceive(UserTypeEnum.MEMBER.getValue(), reqDTO);
  79 + } catch (Exception ex) {
  80 + log.error("[afterReceiveOrder][订单({}) 通知订单收货到微信小程序失败]", order, ex);
  81 + }
  82 + }
  83 +
  84 + // 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
  85 +
  86 +}
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java
@@ -33,6 +33,10 @@ public class PayOrderRespDTO { @@ -33,6 +33,10 @@ public class PayOrderRespDTO {
33 33
34 // ========== 订单相关字段 ========== 34 // ========== 订单相关字段 ==========
35 /** 35 /**
  36 + * 商品标题
  37 + */
  38 + private String subject;
  39 + /**
36 * 支付金额,单位:分 40 * 支付金额,单位:分
37 */ 41 */
38 private Integer price; 42 private Integer price;
@@ -50,4 +54,15 @@ public class PayOrderRespDTO { @@ -50,4 +54,15 @@ public class PayOrderRespDTO {
50 54
51 // ========== 渠道相关字段 ========== 55 // ========== 渠道相关字段 ==========
52 56
  57 + /**
  58 + * 渠道用户编号
  59 + *
  60 + * 例如说,微信 openid、支付宝账号
  61 + */
  62 + private String channelUserId;
  63 + /**
  64 + * 渠道订单号
  65 + */
  66 + private String channelOrderNo;
  67 +
53 } 68 }
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 支付渠道的编码的枚举
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum PayChannelEnum {
  14 +
  15 + WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号网页
  16 + WX_LITE("wx_lite", "微信小程序支付"),
  17 + WX_APP("wx_app", "微信 App 支付"),
  18 + WX_NATIVE("wx_native", "微信 Native 支付"),
  19 + WX_WAP("wx_wap", "微信 Wap 网站支付"), // H5 网页
  20 + WX_BAR("wx_bar", "微信付款码支付"),
  21 +
  22 + ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付"),
  23 + ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付"),
  24 + ALIPAY_APP("alipay_app", "支付宝App 支付"),
  25 + ALIPAY_QR("alipay_qr", "支付宝扫码支付"),
  26 + ALIPAY_BAR("alipay_bar", "支付宝条码支付"),
  27 + MOCK("mock", "模拟支付"),
  28 +
  29 + WALLET("wallet", "钱包支付");
  30 +
  31 + /**
  32 + * 编码
  33 + *
  34 + * 参考 <a href="https://www.pingxx.com/api/支付渠道属性值.html">支付渠道属性值</a>
  35 + */
  36 + private final String code;
  37 + /**
  38 + * 名字
  39 + */
  40 + private final String name;
  41 +
  42 +}
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&lt;WxPayClientC @@ -179,7 +179,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
179 179
180 private PayOrderRespDTO doParseOrderNotifyV3(String body, Map<String, String> headers) throws WxPayException { 180 private PayOrderRespDTO doParseOrderNotifyV3(String body, Map<String, String> headers) throws WxPayException {
181 // 1. 解析回调 181 // 1. 解析回调
182 - SignatureHeader signatureHeader = getRequestHeader(headers); 182 +// SignatureHeader signatureHeader = getRequestHeader(headers);
  183 + SignatureHeader signatureHeader = null;
183 WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, signatureHeader); 184 WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, signatureHeader);
184 WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult(); 185 WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult();
185 // 2. 构建结果 186 // 2. 构建结果
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 { @@ -65,4 +65,20 @@ public interface SocialClientApi {
65 */ 65 */
66 void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO); 66 void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO);
67 67
  68 + /**
  69 + * 上传订单发货到微信小程序
  70 + *
  71 + * @param userType 用户类型
  72 + * @param reqDTO 请求
  73 + */
  74 + void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO);
  75 +
  76 + /**
  77 + * 通知订单收货到微信小程序
  78 + *
  79 + * @param userType 用户类型
  80 + * @param reqDTO 请求
  81 + */
  82 + void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO);
  83 +
68 } 84 }
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaOrderNotifyConfirmReceiveReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.social.dto;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +/**
  10 + * 小程序订单上传购物详情
  11 + *
  12 + * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/shopping-order/normal-shopping-detail/uploadShoppingInfo.html">上传购物详情</a>
  13 + * @author 芋道源码
  14 + */
  15 +@Data
  16 +public class SocialWxaOrderNotifyConfirmReceiveReqDTO {
  17 +
  18 + /**
  19 + * 原支付交易对应的微信订单号
  20 + */
  21 + @NotEmpty(message = "原支付交易对应的微信订单号不能为空")
  22 + private String transactionId;
  23 +
  24 + /**
  25 + * 快递签收时间
  26 + */
  27 + @NotNull(message = "快递签收时间不能为空")
  28 + private LocalDateTime receivedTime;
  29 +
  30 +}
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaOrderUploadShippingInfoReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.social.dto;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * 小程序订单上传购物详情
  9 + *
  10 + * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/shopping-order/normal-shopping-detail/uploadShoppingInfo.html">上传购物详情</a>
  11 + * @author 芋道源码
  12 + */
  13 +@Data
  14 +public class SocialWxaOrderUploadShippingInfoReqDTO {
  15 +
  16 + /**
  17 + * 物流模式 - 实体物流配送采用快递公司进行实体物流配送形式
  18 + */
  19 + public static final Integer LOGISTICS_TYPE_EXPRESS = 1;
  20 + /**
  21 + * 物流模式 - 虚拟商品,虚拟商品,例如话费充值,点卡等,无实体配送形式
  22 + */
  23 + public static final Integer LOGISTICS_TYPE_VIRTUAL = 3;
  24 + /**
  25 + * 物流模式 - 用户自提
  26 + */
  27 + public static final Integer LOGISTICS_TYPE_PICK_UP = 4;
  28 +
  29 + /**
  30 + * 支付者,支付者信息(openid)
  31 + */
  32 + @NotEmpty(message = "支付者,支付者信息(openid)不能为空")
  33 + private String openid;
  34 +
  35 + /**
  36 + * 原支付交易对应的微信订单号
  37 + */
  38 + @NotEmpty(message = "原支付交易对应的微信订单号不能为空")
  39 + private String transactionId;
  40 +
  41 + /**
  42 + * 物流模式
  43 + */
  44 + @NotNull(message = "物流模式不能为空")
  45 + private Integer logisticsType;
  46 + /**
  47 + * 物流发货单号
  48 + */
  49 + private String logisticsNo;
  50 + /**
  51 + * 物流公司编号
  52 + *
  53 + * @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>
  54 + */
  55 + private String expressCompany;
  56 + /**
  57 + * 商品信息
  58 + */
  59 + @NotEmpty(message = "商品信息不能为空")
  60 + private String itemDesc;
  61 + /**
  62 + * 收件人手机号
  63 + */
  64 + @NotEmpty(message = "收件人手机号")
  65 + private String receiverContact;
  66 +
  67 +}
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 { @@ -124,10 +124,11 @@ public interface ErrorCodeConstants {
124 ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR = new ErrorCode(1_002_018_201, "获得小程序码失败"); 124 ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR = new ErrorCode(1_002_018_201, "获得小程序码失败");
125 ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR = new ErrorCode(1_002_018_202, "获得小程序订阅消息模版失败"); 125 ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR = new ErrorCode(1_002_018_202, "获得小程序订阅消息模版失败");
126 ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR = new ErrorCode(1_002_018_203, "发送小程序订阅消息失败"); 126 ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR = new ErrorCode(1_002_018_203, "发送小程序订阅消息失败");
  127 + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR = new ErrorCode(1_002_018_204, "上传微信小程序发货信息失败");
  128 + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_NOTIFY_CONFIRM_RECEIVE_ERROR = new ErrorCode(1_002_018_205, "上传微信小程序订单收货信息失败");
127 ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_210, "社交客户端不存在"); 129 ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_210, "社交客户端不存在");
128 ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_211, "社交客户端已存在配置"); 130 ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_211, "社交客户端已存在配置");
129 131
130 -  
131 // ========== OAuth2 客户端 1-002-020-000 ========= 132 // ========== OAuth2 客户端 1-002-020-000 =========
132 ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在"); 133 ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在");
133 ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1_002_020_001, "OAuth2 客户端编号已存在"); 134 ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1_002_020_001, "OAuth2 客户端编号已存在");
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 { @@ -94,4 +94,14 @@ public class SocialClientApiImpl implements SocialClientApi {
94 socialClientService.sendSubscribeMessage(reqDTO, template.getPriTmplId(), socialUser.getOpenid()); 94 socialClientService.sendSubscribeMessage(reqDTO, template.getPriTmplId(), socialUser.getOpenid());
95 } 95 }
96 96
  97 + @Override
  98 + public void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO) {
  99 + socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO);
  100 + }
  101 +
  102 + @Override
  103 + public void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO) {
  104 + socialClientService.notifyWxaOrderConfirmReceive(userType, reqDTO);
  105 + }
  106 +
97 } 107 }
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; @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.system.service.social;
3 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; 3 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; 5 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO;
  6 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
  7 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
6 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; 8 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
7 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; 9 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
8 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; 10 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
@@ -92,6 +94,22 @@ public interface SocialClientService { @@ -92,6 +94,22 @@ public interface SocialClientService {
92 */ 94 */
93 void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId); 95 void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId);
94 96
  97 + /**
  98 + * 上传订单发货到微信小程序
  99 + *
  100 + * @param userType 用户类型
  101 + * @param reqDTO 请求
  102 + */
  103 + void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO);
  104 +
  105 + /**
  106 + * 通知订单收货到微信小程序
  107 + *
  108 + * @param userType 用户类型
  109 + * @param reqDTO 请求
  110 + */
  111 + void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO);
  112 +
95 // =================== 客户端管理 =================== 113 // =================== 客户端管理 ===================
96 114
97 /** 115 /**
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; @@ -5,11 +5,15 @@ import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
5 import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; 5 import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
6 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; 6 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
7 import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; 7 import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
  8 +import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*;
  9 +import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
8 import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; 10 import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;
9 import cn.binarywang.wx.miniapp.constant.WxMaConstants; 11 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
10 import cn.hutool.core.bean.BeanUtil; 12 import cn.hutool.core.bean.BeanUtil;
11 import cn.hutool.core.collection.CollUtil; 13 import cn.hutool.core.collection.CollUtil;
  14 +import cn.hutool.core.date.LocalDateTimeUtil;
12 import cn.hutool.core.lang.Assert; 15 import cn.hutool.core.lang.Assert;
  16 +import cn.hutool.core.util.DesensitizedUtil;
13 import cn.hutool.core.util.ObjUtil; 17 import cn.hutool.core.util.ObjUtil;
14 import cn.hutool.core.util.ReflectUtil; 18 import cn.hutool.core.util.ReflectUtil;
15 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 19 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -19,6 +23,8 @@ import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; @@ -19,6 +23,8 @@ import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
19 import cn.iocoder.yudao.framework.common.util.http.HttpUtils; 23 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
20 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 24 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
21 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; 25 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO;
  26 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
  27 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
22 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; 28 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
23 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; 29 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
24 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; 30 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
@@ -54,14 +60,17 @@ import org.springframework.data.redis.core.StringRedisTemplate; @@ -54,14 +60,17 @@ import org.springframework.data.redis.core.StringRedisTemplate;
54 import org.springframework.stereotype.Service; 60 import org.springframework.stereotype.Service;
55 61
56 import java.time.Duration; 62 import java.time.Duration;
  63 +import java.time.LocalDateTime;
57 import java.util.List; 64 import java.util.List;
58 import java.util.Map; 65 import java.util.Map;
59 import java.util.Objects; 66 import java.util.Objects;
60 67
  68 +import static cn.hutool.core.date.DatePattern.UTC_MS_WITH_XXX_OFFSET_PATTERN;
61 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 69 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
62 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; 70 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
63 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; 71 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
64 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; 72 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
  73 +import static java.util.Collections.singletonList;
65 74
66 /** 75 /**
67 * 社交应用 Service 实现类 76 * 社交应用 Service 实现类
@@ -327,6 +336,64 @@ public class SocialClientServiceImpl implements SocialClientService { @@ -327,6 +336,64 @@ public class SocialClientServiceImpl implements SocialClientService {
327 return subscribeMessage; 336 return subscribeMessage;
328 } 337 }
329 338
  339 + @Override
  340 + public void uploadWxaOrderShippingInfo(Integer userType, SocialWxaOrderUploadShippingInfoReqDTO reqDTO) {
  341 + WxMaService service = getWxMaService(userType);
  342 + List<ShippingListBean> shippingList;
  343 + if (Objects.equals(reqDTO.getLogisticsType(), SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_EXPRESS)) {
  344 + shippingList = singletonList(ShippingListBean.builder()
  345 + .trackingNo(reqDTO.getLogisticsNo())
  346 + .expressCompany(reqDTO.getExpressCompany())
  347 + .itemDesc(reqDTO.getItemDesc())
  348 + .contact(ContactBean.builder().receiverContact(DesensitizedUtil.mobilePhone(reqDTO.getReceiverContact())).build())
  349 + .build());
  350 + } else {
  351 + shippingList = singletonList(ShippingListBean.builder().itemDesc(reqDTO.getItemDesc()).build());
  352 + }
  353 + WxMaOrderShippingInfoUploadRequest request = WxMaOrderShippingInfoUploadRequest.builder()
  354 + .orderKey(OrderKeyBean.builder()
  355 + .orderNumberType(2) // 使用原支付交易对应的微信订单号,即渠道单号
  356 + .transactionId(reqDTO.getTransactionId())
  357 + .build())
  358 + .logisticsType(reqDTO.getLogisticsType()) // 配送方式
  359 + .deliveryMode(1) // 统一发货
  360 + .shippingList(shippingList)
  361 + .payer(PayerBean.builder().openid(reqDTO.getOpenid()).build())
  362 + .uploadTime(LocalDateTimeUtil.format(LocalDateTime.now(), UTC_MS_WITH_XXX_OFFSET_PATTERN))
  363 + .build();
  364 + try {
  365 + WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().upload(request);
  366 + if (response.getErrCode() != 0) {
  367 + log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({}) response({})]", request, response);
  368 + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, response.getErrMsg());
  369 + }
  370 + log.info("[uploadWxaOrderShippingInfo][上传微信小程序发货信息成功:request({}) response({})]", request, response);
  371 + } catch (WxErrorException ex) {
  372 + log.error("[uploadWxaOrderShippingInfo][上传微信小程序发货信息失败:request({})]", request, ex);
  373 + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR, ex.getError().getErrorMsg());
  374 + }
  375 + }
  376 +
  377 + @Override
  378 + public void notifyWxaOrderConfirmReceive(Integer userType, SocialWxaOrderNotifyConfirmReceiveReqDTO reqDTO) {
  379 + WxMaService service = getWxMaService(userType);
  380 + WxMaOrderShippingInfoNotifyConfirmRequest request = WxMaOrderShippingInfoNotifyConfirmRequest.builder()
  381 + .transactionId(reqDTO.getTransactionId())
  382 + .receivedTime(LocalDateTimeUtil.toEpochMilli(reqDTO.getReceivedTime()))
  383 + .build();
  384 + try {
  385 + WxMaOrderShippingInfoBaseResponse response = service.getWxMaOrderShippingService().notifyConfirmReceive(request);
  386 + if (response.getErrCode() != 0) {
  387 + log.error("[notifyWxaOrderConfirmReceive][确认收货提醒到微信小程序失败:request({}) response({})]", request, response);
  388 + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_NOTIFY_CONFIRM_RECEIVE_ERROR, response.getErrMsg());
  389 + }
  390 + log.info("[notifyWxaOrderConfirmReceive][确认收货提醒到微信小程序成功:request({}) response({})]", request, response);
  391 + } catch (WxErrorException ex) {
  392 + log.error("[notifyWxaOrderConfirmReceive][确认收货提醒到微信小程序失败:request({})]", request, ex);
  393 + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_NOTIFY_CONFIRM_RECEIVE_ERROR, ex.getError().getErrorMsg());
  394 + }
  395 + }
  396 +
330 /** 397 /**
331 * 获得 clientId + clientSecret 对应的 WxMpService 对象 398 * 获得 clientId + clientSecret 对应的 WxMpService 对象
332 * 399 *
yudao-server/src/main/resources/application.yaml
@@ -299,6 +299,7 @@ yudao: @@ -299,6 +299,7 @@ yudao:
299 pay-expire-time: 2h # 支付的过期时间 299 pay-expire-time: 2h # 支付的过期时间
300 receive-expire-time: 14d # 收货的过期时间 300 receive-expire-time: 14d # 收货的过期时间
301 comment-expire-time: 7d # 评论的过期时间 301 comment-expire-time: 7d # 评论的过期时间
  302 + status-sync-to-wxa-enable: true # 是否同步订单状态到微信小程序
302 express: 303 express:
303 client: kd_100 304 client: kd_100
304 kd-niao: 305 kd-niao: