From 5fb7989660a2b5d64331b14e142b538bcb9f15a7 Mon Sep 17 00:00:00 2001 From: Lcp <2767378157@qq.com> Date: Sat, 12 Oct 2024 21:23:12 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E6=8E=A5=E5=85=A5=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=8F=91=E8=B4=A7=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E7=AE=A1=E7=90=86=E5=92=8C=E7=89=A9=E6=B5=81=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao-module-trade-biz/pom.xml | 17 ++ .../admin/wx/wxMaMessageController.java | 144 +++++++++++++++++ .../app/order/AppTradeOrderController.java | 25 ++- .../order/vo/AppTradeOrderDetailRespVO.java | 10 ++ .../order/vo/AppTradeOrderPageItemRespVO.java | 3 + .../convert/order/TradeOrderConvert.java | 9 +- .../dal/dataobject/order/TradeOrderDO.java | 4 + .../wx/WxMaOrderShippingProperties.java | 54 +++++++ .../wx/enums/WxLogisticsTypeEnum.java | 25 +++ .../wx/enums/WxMaDeliveryModeEnum.java | 24 +++ .../delivery/wx/enums/WxOrderNumberType.java | 24 +++ .../order/TradeOrderUpdateServiceImpl.java | 147 +++++++++++++++++- .../src/main/resources/application-local.yaml | 35 +++++ .../src/main/resources/application.yaml | 1 + .../module/pay/api/order/PayOrderApi.java | 6 + .../pay/api/order/dto/PayOrderRespDTO.java | 18 ++- .../module/pay/api/order/PayOrderApiImpl.java | 8 + .../pay/service/order/PayOrderService.java | 7 + .../service/order/PayOrderServiceImpl.java | 5 + 19 files changed, 562 insertions(+), 4 deletions(-) create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/wx/wxMaMessageController.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/WxMaOrderShippingProperties.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxLogisticsTypeEnum.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxMaDeliveryModeEnum.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxOrderNumberType.java diff --git a/yudao-module-mall/yudao-module-trade-biz/pom.xml b/yudao-module-mall/yudao-module-trade-biz/pom.xml index 1c534f18e..f5b9d2809 100644 --- a/yudao-module-mall/yudao-module-trade-biz/pom.xml +++ b/yudao-module-mall/yudao-module-trade-biz/pom.xml @@ -129,6 +129,23 @@ cn.iocoder.cloud yudao-spring-boot-starter-monitor + + + com.github.binarywang + wx-java-mp-spring-boot-starter + + + + com.github.binarywang + wx-java-miniapp-spring-boot-starter + + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-pay + 2.2.0-snapshot + compile + diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/wx/wxMaMessageController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/wx/wxMaMessageController.java new file mode 100644 index 000000000..5fde19929 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/wx/wxMaMessageController.java @@ -0,0 +1,144 @@ +package cn.iocoder.yudao.module.trade.controller.admin.wx; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.WxMaMessage; +import cn.binarywang.wx.miniapp.message.WxMaMessageHandler; +import cn.binarywang.wx.miniapp.message.WxMaMessageRouter; +import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage; +import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; +import com.fasterxml.jackson.core.type.TypeReference; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +/** + * 微信小程序消息推送服务 + *

+ * 参考文档:消息推送 + */ +@RestController +@RequestMapping("/trade/wx") +@Slf4j +public class wxMaMessageController { + + @Resource + private WxMaService wxMaService; + + @Resource + private TradeOrderQueryService tradeOrderQueryService; + + @Resource + private TradeOrderUpdateService tradeOrderUpdateService; + + @Resource + private PayOrderApi payOrderApi; + + private WxMaMessageRouter router; + + @PostConstruct + private void initRouter() { + router = new WxMaMessageRouter(wxMaService); + router + .rule().async(false).event("trade_manage_order_settlement").handler(TradeManageOrderSettlementMessageHandler).end(); + + } + + @RequestMapping("/message") + @Operation(summary = "接收微信推送消息") + @PermitAll + public ResponseEntity receiver(@RequestBody(required = false) String body, @RequestParam Map params) { + + String signature = params.get("signature"); + String nonce = params.get("nonce"); + String timestamp = params.get("timestamp"); + // 检查消息签名 + if (!wxMaService.checkSignature(timestamp, nonce, signature)) { + return ResponseEntity.ok("非法请求"); + } + + log.info("[wxMaMessageController][收到微信推送消息 请求参数({}) 请求体({})]", body, params); + //开发者服务器验证请求 + if (params.containsKey("echostr")) { + return ResponseEntity.ok(params.get("echostr")); + } + WxMaMessage message; + Map context; + if ("JSON".equals(wxMaService.getWxMaConfig().getMsgDataFormat())) { + message = WxMaMessage.fromJson(body); + //明文传输 + if (!params.containsKey("encrypt_type")) { + context = JsonUtils.parseObject(body, new TypeReference>() { + }); + } else {//加密传输 + String decrypt = new WxMaCryptUtils(wxMaService.getWxMaConfig()).decrypt(message.getEncrypt()); + context = JsonUtils.parseObject(decrypt, new TypeReference>() { + }); + } + } else { + //明文传输 + if (!params.containsKey("encrypt_type")) { + message = WxMaMessage.fromXml(body); + } else {//加密传输 + String msgSignature = params.get("msg_signature"); + message = WxMaMessage.fromEncryptedXml(body, wxMaService.getWxMaConfig(), timestamp, nonce, msgSignature); + } + context = message.getAllFieldsMap(); + } + router.route(message, context); + return ResponseEntity.ok(""); + } + + /** + * 处理trade_manage_order_settlement推送消息 + *

+ * 参考文档:小程序发货信息管理服务 + */ + private final WxMaMessageHandler TradeManageOrderSettlementMessageHandler = new WxMaMessageHandler() { + @Override + public WxMaXmlOutMessage handle(WxMaMessage message, Map context, WxMaService wxMaService, WxSessionManager wxSessionManager) { + log.info("[wxMessageHandler][收到(trade_manage_order_settlement)类型事件推送]"); + String transactionId = context.get("transaction_id").toString(); + //包含用户收货时间 + if (context.containsKey("confirm_receive_time")) { + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + //确认收货时间 + String confirmReceiveTime = dateFormat.format(new Date(Long.parseLong(context.get("confirm_receive_time").toString()) * 1000)); + //收货方式(1-自动确认,2-手动确认) + Integer confirmReceiveMethod = Integer.parseInt(context.get("confirm_receive_method").toString()); + + //用交易单号获取支付订单 + log.info("[wxMessageHandler][收到确认收货信息 TransactionId({}) 收货时间({}) 收货方式({})]", transactionId, confirmReceiveTime, confirmReceiveMethod); + + System.out.println("进入处理收货:" + transactionId); + PayOrderRespDTO payOrder = payOrderApi.getOrder(transactionId).getCheckedData(); + //用支付订单获取交易订单 + TradeOrderDO tradeOrderDO = tradeOrderQueryService.getOrder(Long.parseLong(payOrder.getMerchantOrderId())); + //复用会员收货逻辑 + tradeOrderUpdateService.receiveOrderByMember(tradeOrderDO.getUserId(), tradeOrderDO.getId()); + //TODO: 是否需要同步微信端的收货时间和系统的收货时间 + } + + return null; + } + }; +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java index b2b887050..78781210e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java @@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.trade.controller.app.order; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.*; import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; @@ -11,6 +14,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; +import cn.iocoder.yudao.module.trade.framework.delivery.wx.WxMaOrderShippingProperties; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService; import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; @@ -54,6 +58,12 @@ public class AppTradeOrderController { @Resource private TradePriceService priceService; + @Resource + private PayOrderApi payOrderApi; + + @Resource + private WxMaOrderShippingProperties wxMaOrderShippingProperties; + @Resource private TradeOrderProperties tradeOrderProperties; @@ -113,8 +123,13 @@ public class AppTradeOrderController { // 2.2 查询物流公司 DeliveryExpressDO express = order.getLogisticsId() != null && order.getLogisticsId() > 0 ? deliveryExpressService.getDeliveryExpress(order.getLogisticsId()) : null; + //小程序订单查询渠道项 + PayOrderRespDTO payOrder = PayChannelEnum.WX_LITE.getCode().equals(order.getPayChannelCode())? + payOrderApi.getOrder(order.getPayOrderId()).getCheckedData() : null; // 2.3 最终组合 - return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express)); + return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express, payOrder, + wxMaOrderShippingProperties.getIsMaTradeManaged())); + } @GetMapping("/get-express-track-list") @@ -168,6 +183,14 @@ public class AppTradeOrderController { return success(true); } + @PutMapping("/wx-receive") + @Operation(summary = "微信小程序确认交易订单收货") + public CommonResult receiveWxOrder(@RequestParam("channelOrderNo") String channelOrderNo) { + PayOrderRespDTO payOrder = payOrderApi.getOrder(channelOrderNo).getCheckedData(); + tradeOrderUpdateService.receiveOrderByMember(getLoginUserId(), Long.valueOf(payOrder.getMerchantOrderId())); + return success(true); + } + @DeleteMapping("/cancel") @Operation(summary = "取消交易订单") @Parameter(name = "id", description = "交易订单编号") diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderDetailRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderDetailRespVO.java index b91bbf1a6..ddc26721b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderDetailRespVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderDetailRespVO.java @@ -59,9 +59,16 @@ public class AppTradeOrderDetailRespVO { @Schema(description = "支付渠道", example = "wx_lite_pay") private String payChannelCode; + @Schema(description = "支付渠道名", example = "微信小程序支付") private String payChannelName; + @Schema(description = "支付渠道订单号", example = "4200008888197001018888888888") + private String channelOrderNo; + + @Schema(description = "是否开启微信小程序发货信息管理") + private Boolean isMaTradeManaged; + @Schema(description = "商品原价(总)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") private Integer totalPrice; @@ -91,6 +98,9 @@ public class AppTradeOrderDetailRespVO { @Schema(description = "发货物流单号", example = "1024") private String logisticsNo; + @Schema(description = "微信物流查询id") + private String waybillToken; + @Schema(description = "发货时间") private LocalDateTime deliveryTime; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageItemRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageItemRespVO.java index ba7b8138c..cc19b6377 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageItemRespVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageItemRespVO.java @@ -45,6 +45,9 @@ public class AppTradeOrderPageItemRespVO { @Schema(description = "配送方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer deliveryType; + @Schema(description = "微信物流查询id") + private String waybillToken; + /** * 订单项数组 */ diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java index 45462a5be..6deb78afe 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; @@ -174,7 +175,9 @@ public interface TradeOrderConvert { default AppTradeOrderDetailRespVO convert02(TradeOrderDO order, List orderItems, TradeOrderProperties tradeOrderProperties, - DeliveryExpressDO express) { + DeliveryExpressDO express, + PayOrderRespDTO payOrder, + Boolean isMaTradeManaged) { AppTradeOrderDetailRespVO orderVO = convert3(order, orderItems); orderVO.setPayExpireTime(order.getCreateTime().plus(tradeOrderProperties.getPayExpireTime())); if (StrUtil.isNotEmpty(order.getPayChannelCode())) { @@ -185,6 +188,10 @@ public interface TradeOrderConvert { if (express != null) { orderVO.setLogisticsId(express.getId()).setLogisticsName(express.getName()); } + //处理支付渠道订单号 + if(payOrder != null) + orderVO.setChannelOrderNo(payOrder.getChannelOrderNo()); + orderVO.setIsMaTradeManaged(isMaTradeManaged); return orderVO; } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 4c06c8013..01c6cbd80 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -210,6 +210,10 @@ public class TradeOrderDO extends BaseDO { * 如果无需发货,则 logisticsNo 设置 ""。原因是,不想再添加额外字段 */ private String logisticsNo; + /** + * 微信物流查询id + */ + private String waybillToken; /** * 发货时间 */ diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/WxMaOrderShippingProperties.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/WxMaOrderShippingProperties.java new file mode 100644 index 000000000..1a0e35d94 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/WxMaOrderShippingProperties.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.trade.framework.delivery.wx; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.hutool.core.util.StrUtil; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.Data; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class WxMaOrderShippingProperties { + + @Resource + private WxMaService wxMaService; + + @Resource + private WxMaProperties wxMaProperties; + + /** + * 是否开启微信小程序发货信息管理 + */ + private Boolean isMaTradeManaged = false; + + /** + * 是否使用微信小程序物流查询插件 + */ + @Value("${wx.miniapp.plugin.logistics}") + private Boolean useMaLogisticsPlugin; + + /** + * 订单详情路径 + */ + @Value("${wx.miniapp.plugin.order-detail-path}") + private String orderDetailPath; + + /** + * 判断是否开启微信小程序发货信息管理 + */ + @PostConstruct + private void checkMpTradeManaged(){ + try { + if (StrUtil.isNotBlank(wxMaProperties.getAppid())) + isMaTradeManaged = wxMaService.getWxMaOrderShippingService().isTradeManaged(wxMaProperties.getAppid()).getTradeManaged(); + }catch (WxErrorException e){ + throw new WxRuntimeException(e.getError().getErrorMsg()); + } + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxLogisticsTypeEnum.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxLogisticsTypeEnum.java new file mode 100644 index 000000000..d849a0cc3 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxLogisticsTypeEnum.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.trade.framework.delivery.wx.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 小程序发货信息管理服务的发货方式枚举 + *

+ * 参考文档: + * + */ +@Getter +@AllArgsConstructor +public enum WxLogisticsTypeEnum { + + EXPRESS(1,"物流配送"), + INTRA_CITY(2,"同城配送"), + VIRTUAL(3,"虚拟商品"), + PICK_UP(4,"用户自提"); + + + private final Integer code; + + private final String desc; +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxMaDeliveryModeEnum.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxMaDeliveryModeEnum.java new file mode 100644 index 000000000..38b1bd928 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxMaDeliveryModeEnum.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.trade.framework.delivery.wx.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 小程序发货信息管理服务的发货模式枚举 + *

+ * 参考文档: + * + */ +@Getter +@AllArgsConstructor +public enum WxMaDeliveryModeEnum { + + UNIFIED_DELIVERY(1,"统一发货"), + + SPLIT_DELIVERY(2,"分拆发货"); + + private final Integer code; + + private final String desc; + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxOrderNumberType.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxOrderNumberType.java new file mode 100644 index 000000000..7d6a5c7ac --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/wx/enums/WxOrderNumberType.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.trade.framework.delivery.wx.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 小程序发货信息管理服务的订单单号类型枚举 + *

+ * 参考文档: + * + */ +@Getter +@AllArgsConstructor +public enum WxOrderNumberType { + + MCH(1,"商户号和商户侧单号"), + + TRANSACTION(2,"微信支付单号"); + + private final Integer code; + + private final String desc; + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index a199011ba..33f25d71b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -1,5 +1,9 @@ package cn.iocoder.yudao.module.trade.service.order; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.delivery.TraceWaybillRequest; +import cn.binarywang.wx.miniapp.bean.delivery.WaybillGoodsInfo; +import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.Assert; @@ -12,6 +16,7 @@ import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; @@ -42,6 +47,10 @@ import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.*; +import cn.iocoder.yudao.module.trade.framework.delivery.wx.WxMaOrderShippingProperties; +import cn.iocoder.yudao.module.trade.framework.delivery.wx.enums.WxLogisticsTypeEnum; +import cn.iocoder.yudao.module.trade.framework.delivery.wx.enums.WxMaDeliveryModeEnum; +import cn.iocoder.yudao.module.trade.framework.delivery.wx.enums.WxOrderNumberType; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog; import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils; @@ -57,15 +66,20 @@ import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculat import jakarta.annotation.Resource; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -104,6 +118,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Resource private TradeMessageService tradeMessageService; + @Resource + private WxMaService wxMaService; + + @Resource + private WxMaOrderShippingProperties wxMaOrderShippingProperties; + @Resource private PayOrderApi payOrderApi; @Resource @@ -368,6 +388,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { throw exception(ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS); } + PayOrderRespDTO payOrder = null; + //小程序发货时获取支付单 + if(PayChannelEnum.WX_LITE.getCode().equals(order.getPayChannelCode()) + && (wxMaOrderShippingProperties.getIsMaTradeManaged() || wxMaOrderShippingProperties.getUseMaLogisticsPlugin())) { + payOrder = payOrderApi.getOrder(order.getPayOrderId()).getCheckedData(); + } + // 2. 更新订单为已发货 TradeOrderDO updateOrderObj = new TradeOrderDO(); // 2.1 快递发货 @@ -375,12 +402,23 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { if (ObjectUtil.notEqual(deliveryReqVO.getLogisticsId(), TradeOrderDO.LOGISTICS_ID_NULL)) { express = deliveryExpressService.validateDeliveryExpress(deliveryReqVO.getLogisticsId()); updateOrderObj.setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo()); + //使用微信小程序物流查询插件 + if(payOrder!=null && wxMaOrderShippingProperties.getUseMaLogisticsPlugin()){ + try { + String waybillToken = getWxTraceWaybillToken(order.getId(), deliveryReqVO.getLogisticsNo(), express, order.getReceiverMobile(), payOrder); + updateOrderObj.setWaybillToken(waybillToken); + }catch (WxErrorException e) { + log.error("[WxMaLogisticsPlugin][微信小程序物流插件查询id获取发生异常,异常信息({})]",e.getMessage()); + throw new RuntimeException(e); + } + } } else { // 2.2 无需发货 updateOrderObj.setLogisticsId(0L).setLogisticsNo(""); } + LocalDateTime deliveryTime = LocalDateTime.now(); // 执行更新 - updateOrderObj.setStatus(TradeOrderStatusEnum.DELIVERED.getStatus()).setDeliveryTime(LocalDateTime.now()); + updateOrderObj.setStatus(TradeOrderStatusEnum.DELIVERED.getStatus()).setDeliveryTime(deliveryTime); int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateOrderObj); if (updateCount == 0) { throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED); @@ -396,6 +434,113 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null)); // 4.2 发送订阅消息 getSelf().sendDeliveryOrderMessage(order, deliveryReqVO); + + //小程序发货信息录入,放最后确保数据库事务完成 + if(payOrder!= null && wxMaOrderShippingProperties.getIsMaTradeManaged()) + wxMaOrderUpload(order.getId(), updateOrderObj, express, order.getReceiverMobile(), payOrder, deliveryTime); + } + + /** + * 微信小程序交易发货信息录入 + * + * @param orderId 交易订单id + * @param updateOrderObj 更新交易订单对象 + * @param express 发货请求 + * @param receiverMobile 收件人手机号码 + * @param payOrder 交易订单 + * @param deliveryTime 发货时间 + */ + private void wxMaOrderUpload(Long orderId, TradeOrderDO updateOrderObj, DeliveryExpressDO express, + String receiverMobile, PayOrderRespDTO payOrder, LocalDateTime deliveryTime) { + //构建订单信息 + OrderKeyBean orderKey = new OrderKeyBean(WxOrderNumberType.TRANSACTION.getCode(),payOrder.getChannelOrderNo(),null,null); + + //默认为快递发货 + int logistics_type = WxLogisticsTypeEnum.EXPRESS.getCode(); + + if(ObjectUtil.equal(updateOrderObj.getLogisticsId(), TradeOrderDO.LOGISTICS_ID_NULL)){ + //无需发货的场景 + //TODO: 无需发货只设置为虚拟发货 + logistics_type = WxLogisticsTypeEnum.VIRTUAL.getCode(); + } + + + //构建物流信息列表,合并发货所以只有一条记录 + List shippingList = new ArrayList<>(){{ + add( + ShippingListBean.builder() + .trackingNo(updateOrderObj.getLogisticsNo()) + .expressCompany(express!=null?express.getCode():null) + .itemDesc(payOrder.getBody()) + .contact(new ContactBean(null,receiverMobile)) + .build() + ); + }}; + + //根据接口要求生成时间字符串,同步平台生成的时间 + ZonedDateTime dateTime = ZonedDateTime.of(deliveryTime, ZoneId.of("Asia/Shanghai")); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyy-MM-dd'T'HH:mm:ss.SSSXXX"); + String upload_time = dateTime.format(formatter); + + //构建请求 + WxMaOrderShippingInfoUploadRequest request = WxMaOrderShippingInfoUploadRequest.builder() + .orderKey(orderKey) + .deliveryMode(WxMaDeliveryModeEnum.UNIFIED_DELIVERY.getCode())//仅考虑合并发货情况 + .logisticsType(logistics_type) + .isAllDelivered(true) + .shippingList(shippingList) + .uploadTime(upload_time) + .payer(new PayerBean(payOrder.getChannelUserId())) + .build(); + try{ + //上传发货信息 + wxMaService.getWxMaOrderShippingService().upload(request); + //设置订单详情路径 + wxMaService.getWxMaOrderShippingService().setMsgJumpPath(wxMaOrderShippingProperties.getOrderDetailPath()+"?id="+orderId); + } catch (WxErrorException e) { + log.error("[WxMaOrderShipping][微信小程序发货信息录入发生异常,异常信息({})]",e.getMessage()); + throw new RuntimeException(e); + } + } + + /** + * 获取微信小程序物流查询插件的查询id(waybillToken) + * + * @param orderId 订单id + * @param logisticsNo 元旦号 + * @param express 快递公司 + * @param receiverMobile 收件人手机号 + * @param payOrder 交易订单 + * @return 物流查询id + */ + private String getWxTraceWaybillToken(Long orderId, String logisticsNo, DeliveryExpressDO express, + String receiverMobile, PayOrderRespDTO payOrder) throws WxErrorException { + + List orderItems = tradeOrderItemMapper.selectListByOrderId(orderId); + List goodsItems = orderItems + .stream() + .map(item->{ + WaybillGoodsInfo.GoodsItem goodsItem = new WaybillGoodsInfo.GoodsItem(); + goodsItem.setGoodsName(item.getSpuName()); + goodsItem.setGoodsImgUrl(item.getPicUrl()); + return goodsItem; + }) + .collect(Collectors.toList()); + + //构建请求 + //TODO: WxJava 4.6.0暂时不支持设置快递公司代码, 之后最好加上 + TraceWaybillRequest request = TraceWaybillRequest.builder() + .openid(payOrder.getChannelUserId()) + .senderPhone("") + .receiverPhone(receiverMobile) + //.deliveryId(express.getCode()) + .waybillId(logisticsNo) + .goodsInfo(new WaybillGoodsInfo(goodsItems)) + .transId(payOrder.getChannelOrderNo()) + .orderDetailPath(wxMaOrderShippingProperties.getOrderDetailPath() + "?id=" + orderId) + .build(); + + return wxMaService.getWxMaImmediateDeliveryService().traceWaybill(request).getWaybillToken(); } @Async diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml index ec712893c..8bac79cc9 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml @@ -125,6 +125,41 @@ logging: cn.iocoder.yudao.module.trade.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 +--- #################### 微信公众号、小程序相关配置 #################### +wx: + mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档 + # app-id: wx041349c6f39b268b # 测试号(牛希尧提供的) + # secret: 5abee519483bc9f8cb37ce280e814bd0 + app-id: wx5b23ba7a5589ecbb # 测试号(自己的) + secret: 2a7b3b20c537e52e74afd395eb85f61f + # app-id: wxa69ab825b163be19 # 测试号(Kongdy 提供的) + # secret: bd4f9fab889591b62aeac0d7b8d8b4a0 + # 存储配置,解决 AccessToken 的跨节点的共享 + config-storage: + type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 + key-prefix: wx # Redis Key 的前缀 + http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 + miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档 + # appid: wx62056c0d5e8db250 # 测试号(牛希尧提供的) + # secret: 333ae72f41552af1e998fe1f54e1584a + # appid: wx63c280fe3248a3e7 # wenhualian的接口测试号 + # secret: 6f270509224a7ae1296bbf1c8cb97aed + # appid: wxc4598c446f8a9cb3 # 测试号(Kongdy 提供的) + # secret: 4a1a04e07f6a4a0751b39c3064a92c8b + appid: wx66186af0759f47c9 # 测试号(puhui 提供的) + secret: 3218bcbd112cbc614c7264ceb20144ac + config-storage: + type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 + key-prefix: wa # Redis Key 的前缀 + http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 + token: # 微信小程序消息推送服务Token + aes-key: # 微信小程序消息推送服务AES kEY + msg-data-format: JSON # 微信小程序消息推送服务推送信息格式,只适配了JSON + plugin: + logistics: true + order-detail-path: /pages/order/detail + + --- #################### 芋道相关配置 #################### # 芋道配置项,设置当前项目所有自定义的配置 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml index a64bed4d1..c2e3b420c 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml @@ -122,6 +122,7 @@ yudao: tenant: # 多租户相关配置项 enable: true ignore-urls: + - /admin-api/trade/wx/** # 接收微信消息推送,不携带租户编号 ignore-tables: trade: order: diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java index c0d818d3b..3a28f8dcb 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java @@ -30,6 +30,12 @@ public interface PayOrderApi { @PermitAll CommonResult getOrder(@RequestParam("id") Long id); + @GetMapping(PREFIX + "/get-by-channel") + @Operation(summary = "获得支付单") + @Parameter(name = "channelOrderNo", description = "渠道订单号", required = true) + @PermitAll + CommonResult getOrder(@RequestParam("channelOrderNo") String channelOrderNo); + @PutMapping(PREFIX + "/update-price") @Operation(summary = "更新支付订单价格") @Parameters({ diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java index 583d6d54d..945395088 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java @@ -28,6 +28,14 @@ public class PayOrderRespDTO { * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一 */ private String merchantOrderId; + /** + * 商品标题 + */ + private String subject; + /** + * 商品描述信息 + */ + private String body; // ========== 订单相关字段 ========== /** @@ -36,11 +44,19 @@ public class PayOrderRespDTO { private Integer price; /** * 支付状态 - * * 枚举 {@link PayOrderStatusEnum} */ private Integer status; // ========== 渠道相关字段 ========== + /** + * 渠道用户编号 + * 例如说,微信 openid、支付宝账号 + */ + private String channelUserId; + /** + * 渠道订单号 + */ + private String channelOrderNo; } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java index 1ca3414db..1a1e77509 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.pay.api.order; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; @@ -31,6 +32,13 @@ public class PayOrderApiImpl implements PayOrderApi { return success(PayOrderConvert.INSTANCE.convert2(order)); } + @Override + @TenantIgnore + public CommonResult getOrder(String channelOrderNo) { + PayOrderDO order = payOrderService.getOrder(channelOrderNo); + return success(PayOrderConvert.INSTANCE.convert2(order)); + } + @Override public CommonResult updatePayOrderPrice(Long id, Integer payPrice) { payOrderService.updatePayOrderPrice(id, payPrice); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java index b848b53e3..abd5e3289 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java @@ -31,6 +31,13 @@ public interface PayOrderService { */ PayOrderDO getOrder(Long id); + /** + * 获得支付订单 + * + * @param channalOrderNo 渠道订单号 + * @return 支付订单 + */ + PayOrderDO getOrder(String channalOrderNo); /** * 获得支付订单 * diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java index b03d236a0..7b5f2c34a 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java @@ -80,6 +80,11 @@ public class PayOrderServiceImpl implements PayOrderService { return orderMapper.selectById(id); } + @Override + public PayOrderDO getOrder(String channelOrderNo) { + return orderMapper.selectOne(PayOrderDO::getChannelOrderNo, channelOrderNo); + } + @Override public PayOrderDO getOrder(Long appId, String merchantOrderId) { return orderMapper.selectByAppIdAndMerchantOrderId(appId, merchantOrderId);