Pre Merge pull request !145 from Lcp/pr
commit
8ea2d47fba
|
@ -129,6 +129,23 @@
|
|||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java-mp-spring-boot-starter</artifactId> <!-- 微信登录(公众号) -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId> <!-- 微信登录(小程序) -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
|
||||
<version>2.2.0-snapshot</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 微信小程序消息推送服务
|
||||
* <p>
|
||||
* 参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html">消息推送</a>
|
||||
*/
|
||||
@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<String, String> 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<String, Object> context;
|
||||
if ("JSON".equals(wxMaService.getWxMaConfig().getMsgDataFormat())) {
|
||||
message = WxMaMessage.fromJson(body);
|
||||
//明文传输
|
||||
if (!params.containsKey("encrypt_type")) {
|
||||
context = JsonUtils.parseObject(body, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
} else {//加密传输
|
||||
String decrypt = new WxMaCryptUtils(wxMaService.getWxMaConfig()).decrypt(message.getEncrypt());
|
||||
context = JsonUtils.parseObject(decrypt, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
}
|
||||
} 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推送消息
|
||||
* <p>
|
||||
* 参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html">小程序发货信息管理服务</a>
|
||||
*/
|
||||
private final WxMaMessageHandler TradeManageOrderSettlementMessageHandler = new WxMaMessageHandler() {
|
||||
@Override
|
||||
public WxMaXmlOutMessage handle(WxMaMessage message, Map<String, Object> 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;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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<Boolean> 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 = "交易订单编号")
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -45,6 +45,9 @@ public class AppTradeOrderPageItemRespVO {
|
|||
@Schema(description = "配送方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer deliveryType;
|
||||
|
||||
@Schema(description = "微信物流查询id")
|
||||
private String waybillToken;
|
||||
|
||||
/**
|
||||
* 订单项数组
|
||||
*/
|
||||
|
|
|
@ -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<TradeOrderItemDO> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -210,6 +210,10 @@ public class TradeOrderDO extends BaseDO {
|
|||
* 如果无需发货,则 logisticsNo 设置 ""。原因是,不想再添加额外字段
|
||||
*/
|
||||
private String logisticsNo;
|
||||
/**
|
||||
* 微信物流查询id
|
||||
*/
|
||||
private String waybillToken;
|
||||
/**
|
||||
* 发货时间
|
||||
*/
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package cn.iocoder.yudao.module.trade.framework.delivery.wx.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 小程序发货信息管理服务的发货方式枚举
|
||||
*<p>
|
||||
* 参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html">
|
||||
*
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum WxLogisticsTypeEnum {
|
||||
|
||||
EXPRESS(1,"物流配送"),
|
||||
INTRA_CITY(2,"同城配送"),
|
||||
VIRTUAL(3,"虚拟商品"),
|
||||
PICK_UP(4,"用户自提");
|
||||
|
||||
|
||||
private final Integer code;
|
||||
|
||||
private final String desc;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cn.iocoder.yudao.module.trade.framework.delivery.wx.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 小程序发货信息管理服务的发货模式枚举
|
||||
*<p>
|
||||
* 参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html">
|
||||
*
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum WxMaDeliveryModeEnum {
|
||||
|
||||
UNIFIED_DELIVERY(1,"统一发货"),
|
||||
|
||||
SPLIT_DELIVERY(2,"分拆发货");
|
||||
|
||||
private final Integer code;
|
||||
|
||||
private final String desc;
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cn.iocoder.yudao.module.trade.framework.delivery.wx.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 小程序发货信息管理服务的订单单号类型枚举
|
||||
*<p>
|
||||
* 参考文档:<a href="https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html">
|
||||
*
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum WxOrderNumberType {
|
||||
|
||||
MCH(1,"商户号和商户侧单号"),
|
||||
|
||||
TRANSACTION(2,"微信支付单号");
|
||||
|
||||
private final Integer code;
|
||||
|
||||
private final String desc;
|
||||
|
||||
}
|
|
@ -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<ShippingListBean> 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<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
|
||||
List<WaybillGoodsInfo.GoodsItem> 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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
# 芋道配置项,设置当前项目所有自定义的配置
|
||||
|
|
|
@ -122,6 +122,7 @@ yudao:
|
|||
tenant: # 多租户相关配置项
|
||||
enable: true
|
||||
ignore-urls:
|
||||
- /admin-api/trade/wx/** # 接收微信消息推送,不携带租户编号
|
||||
ignore-tables:
|
||||
trade:
|
||||
order:
|
||||
|
|
|
@ -30,6 +30,12 @@ public interface PayOrderApi {
|
|||
@PermitAll
|
||||
CommonResult<PayOrderRespDTO> getOrder(@RequestParam("id") Long id);
|
||||
|
||||
@GetMapping(PREFIX + "/get-by-channel")
|
||||
@Operation(summary = "获得支付单")
|
||||
@Parameter(name = "channelOrderNo", description = "渠道订单号", required = true)
|
||||
@PermitAll
|
||||
CommonResult<PayOrderRespDTO> getOrder(@RequestParam("channelOrderNo") String channelOrderNo);
|
||||
|
||||
@PutMapping(PREFIX + "/update-price")
|
||||
@Operation(summary = "更新支付订单价格")
|
||||
@Parameters({
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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<PayOrderRespDTO> getOrder(String channelOrderNo) {
|
||||
PayOrderDO order = payOrderService.getOrder(channelOrderNo);
|
||||
return success(PayOrderConvert.INSTANCE.convert2(order));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> updatePayOrderPrice(Long id, Integer payPrice) {
|
||||
payOrderService.updatePayOrderPrice(id, payPrice);
|
||||
|
|
|
@ -31,6 +31,13 @@ public interface PayOrderService {
|
|||
*/
|
||||
PayOrderDO getOrder(Long id);
|
||||
|
||||
/**
|
||||
* 获得支付订单
|
||||
*
|
||||
* @param channalOrderNo 渠道订单号
|
||||
* @return 支付订单
|
||||
*/
|
||||
PayOrderDO getOrder(String channalOrderNo);
|
||||
/**
|
||||
* 获得支付订单
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue