+ *
+ */
+@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);