Commit a34de9f2239cedfcba3ff189c76df36bb6c7b615
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<WxPayClientC | @@ -179,7 +179,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<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: |