diff --git a/consumer/src/main/java/cn/lili/event/impl/WechatMPExecute.java b/consumer/src/main/java/cn/lili/event/impl/WechatMPExecute.java new file mode 100644 index 00000000..c4f74715 --- /dev/null +++ b/consumer/src/main/java/cn/lili/event/impl/WechatMPExecute.java @@ -0,0 +1,48 @@ +package cn.lili.event.impl; + +import cn.lili.event.OrderStatusChangeEvent; +import cn.lili.modules.order.order.entity.dto.OrderMessage; +import cn.lili.modules.wechat.service.WechatMPService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service ; + +/** + * 微信小程序执行器 + * + * @author Chopper + * @version v1.0 2021-04-19 14:25 + */ +@Slf4j +@Service +public class WechatMPExecute implements OrderStatusChangeEvent { + + @Autowired + private WechatMPService wechatMPService; + + + /** + * 订单已发货、待提货、待核验状态 如果是微信小程序的订单则进行 订单发货信息录入 + * + * @param orderMessage 订单消息 + */ + @Override + public void orderChange(OrderMessage orderMessage) { + + switch (orderMessage.getNewStatus()) { + case TAKE: + case STAY_PICKED_UP: + case DELIVERED: + try { + wechatMPService.uploadShippingInfo(orderMessage.getOrderSn()); + } catch (Exception e) { + log.error("发货信息录入失败", e); + } + break; + default: + break; + } + } + + +} diff --git a/framework/src/main/java/cn/lili/modules/payment/kit/plugin/wechat/WechatPlugin.java b/framework/src/main/java/cn/lili/modules/payment/kit/plugin/wechat/WechatPlugin.java index dee5282b..5a27dbf9 100644 --- a/framework/src/main/java/cn/lili/modules/payment/kit/plugin/wechat/WechatPlugin.java +++ b/framework/src/main/java/cn/lili/modules/payment/kit/plugin/wechat/WechatPlugin.java @@ -20,6 +20,7 @@ import cn.lili.modules.connect.entity.Connect; import cn.lili.modules.connect.entity.enums.SourceEnum; import cn.lili.modules.connect.service.ConnectService; import cn.lili.modules.member.entity.dto.ConnectQueryDTO; +import cn.lili.modules.order.order.entity.dos.Order; import cn.lili.modules.order.order.service.OrderService; import cn.lili.modules.payment.entity.RefundLog; import cn.lili.modules.payment.entity.enums.PaymentMethodEnum; @@ -48,6 +49,7 @@ import cn.lili.modules.system.service.SettingService; import cn.lili.modules.wallet.entity.dos.MemberWithdrawApply; import cn.lili.modules.wallet.entity.dto.TransferResultDTO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -161,6 +163,8 @@ public class WechatPlugin implements Payment { JSONUtil.toJsonStr(unifiedOrderModel) ); + updateOrderPayNo(payParam,outOrderNo); + return ResultUtil.data(JSONUtil.toJsonStr(response.getBody())); } catch (Exception e) { log.error("微信H5支付错误", e); @@ -232,6 +236,8 @@ public class WechatPlugin implements Payment { Map map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key()); log.info("唤起支付参数:{}", map); + updateOrderPayNo(payParam,outOrderNo); + return ResultUtil.data(map); } log.error("微信支付参数验证错误,请及时处理"); @@ -299,6 +305,8 @@ public class WechatPlugin implements Payment { setting.getApiclient_key(), SignType.MD5); log.info("唤起支付参数:{}", map); + updateOrderPayNo(payParam,outOrderNo); + return ResultUtil.data(map); } log.error("微信支付参数验证错误,请及时处理"); @@ -359,6 +367,8 @@ public class WechatPlugin implements Payment { log.info("verifySignature: {}", verifySignature); if (verifySignature) { + updateOrderPayNo(payParam,outOrderNo); + return ResultUtil.data(new JSONObject(response.getBody()).getStr("code_url")); } else { log.error("微信支付参数验证错误,请及时处理"); @@ -438,7 +448,7 @@ public class WechatPlugin implements Payment { String prepayId = jsonObject.getStr("prepay_id"); Map map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key()); log.info("唤起支付参数:{}", map); - + updateOrderPayNo(payParam,outOrderNo); return ResultUtil.data(map); } log.error("微信支付参数验证错误,请及时处理"); @@ -783,4 +793,21 @@ public class WechatPlugin implements Payment { cipherText ); } + + /** + * 修改订单支付单号 + * @param payParam 支付参数 + * @param outOrderNo 订单号 + */ + private void updateOrderPayNo(PayParam payParam,String outOrderNo ){ + if(payParam.getOrderType().equals("ORDER")){ + orderService.update(new LambdaUpdateWrapper() + .eq(Order::getSn,payParam.getSn()) + .set(Order::getPayOrderNo,outOrderNo)); + }else if(payParam.getOrderType().equals("TRADE")){ + orderService.update(new LambdaUpdateWrapper() + .eq(Order::getTradeSn,payParam.getSn()) + .set(Order::getPayOrderNo,outOrderNo)); + } + } } diff --git a/framework/src/main/java/cn/lili/modules/wechat/service/WechatMPService.java b/framework/src/main/java/cn/lili/modules/wechat/service/WechatMPService.java new file mode 100644 index 00000000..b41902ac --- /dev/null +++ b/framework/src/main/java/cn/lili/modules/wechat/service/WechatMPService.java @@ -0,0 +1,14 @@ +package cn.lili.modules.wechat.service; +/** + * 微信小程序 业务层 + * @author Chopper + */ +public interface WechatMPService { + + + /** + * 微信小程序-上传发货信息 + * @param orderSn + */ + void uploadShippingInfo(String orderSn); +} diff --git a/framework/src/main/java/cn/lili/modules/wechat/serviceimpl/WechatMPServiceImpl.java b/framework/src/main/java/cn/lili/modules/wechat/serviceimpl/WechatMPServiceImpl.java new file mode 100644 index 00000000..73cb6f91 --- /dev/null +++ b/framework/src/main/java/cn/lili/modules/wechat/serviceimpl/WechatMPServiceImpl.java @@ -0,0 +1,313 @@ +package cn.lili.modules.wechat.serviceimpl; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.lili.common.enums.ClientTypeEnum; +import cn.lili.common.exception.ServiceException; +import cn.lili.common.security.context.UserContext; +import cn.lili.common.utils.HttpUtils; +import cn.lili.modules.connect.entity.Connect; +import cn.lili.modules.connect.entity.enums.SourceEnum; +import cn.lili.modules.connect.service.ConnectService; +import cn.lili.modules.member.entity.dto.ConnectQueryDTO; +import cn.lili.modules.order.order.entity.dos.Order; +import cn.lili.modules.order.order.entity.dos.OrderItem; +import cn.lili.modules.order.order.entity.enums.OrderStatusEnum; +import cn.lili.modules.order.order.service.OrderItemService; +import cn.lili.modules.order.order.service.OrderService; +import cn.lili.modules.system.entity.dos.Setting; +import cn.lili.modules.system.entity.dto.payment.WechatPaymentSetting; +import cn.lili.modules.system.entity.enums.SettingEnum; +import cn.lili.modules.system.service.SettingService; +import cn.lili.modules.wechat.service.WechatMPService; +import cn.lili.modules.wechat.util.WechatAccessTokenUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 微信小程序 业务实现 + * + * @author Bulbasaur + */ +@Service +@Slf4j +public class WechatMPServiceImpl implements WechatMPService { + + @Autowired + private OrderService orderService; + @Autowired + private OrderItemService orderItemService; + @Autowired + private ConnectService connectService; + @Autowired + private WechatAccessTokenUtil wechatAccessTokenUtil; + @Autowired + private SettingService settingService; + + /** + * 发货信息接口 + */ + String url = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token="; + /** + * 物流公司列表 + */ + String DeliveryUrl = "https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/get_delivery_list?access_token="; + /** + * 是否开通发货信息管理 + */ + String isTradeManagedUrl = "https://api.weixin.qq.com/wxa/sec/order/is_trade_managed?access_token="; + + /** + * 发货信息录入 + * + * @param orderSn 订单号 + */ + @Override + public void uploadShippingInfo(String orderSn) { + + Order order = orderService.getBySn(orderSn); + //是否是微信小程序订单 && 微信支付 + if (!order.getClientType().equals(ClientTypeEnum.WECHAT_MP.name()) + || !order.getPaymentMethod().equals(ClientTypeEnum.WECHAT_MP.name())) { + return; + } + //是否开通发货信息管理 + if (!isTradeManaged()) { + return; + } + + Map map = new HashMap<>(2); + + //发货信息录入 + //订单,需要上传物流信息的订单 + map.put("order_key", new OrderKey(order)); + + + if (order.getOrderStatus().equals(OrderStatusEnum.TAKE.name())) { + + //物流模式,发货方式枚举值:1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品,虚拟商品,例如话费充值,点卡等,无实体配送形式 4、用户自提 + map.put("logistics_type", 3); + //发货模式,发货模式枚举值:1、UNIFIED_DELIVERY(统一发货)2、SPLIT_DELIVERY(分拆发货) 示例值: UNIFIED_DELIVERY + map.put("delivery_mode", 1); + + + } else if (order.getOrderStatus().equals(OrderStatusEnum.DELIVERED.name())) { + + //物流模式,发货方式枚举值:1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品,虚拟商品,例如话费充值,点卡等,无实体配送形式 4、用户自提 + map.put("logistics_type", 1); + //发货模式,发货模式枚举值:1、UNIFIED_DELIVERY(统一发货)2、SPLIT_DELIVERY(分拆发货) 示例值: UNIFIED_DELIVERY + map.put("delivery_mode", 1); + + //物流信息列表,发货物流单列表,支持统一发货(单个物流单)和分拆发货(多个物流单)两种模式,多重性: [1, 10] + List shippingList = new ArrayList<>(); + Shipping shipping = new Shipping(); + shipping.setTracking_no(order.getLogisticsNo()); + shipping.setExpress_company(getDeliveryList(order)); + //商品信息,例如:微信红包抱枕*1个,限120个字以内 + List orderItemList = orderItemService.getByOrderSn(order.getSn()); + shipping.setItem_desc(orderItemList.get(0).getGoodsName()); + //联系方式,当发货的物流公司为顺丰时,联系方式为必填,收件人或寄件人联系方式二选一 + Contact contact = new Contact(); + contact.setReceiver_contact(order.getConsigneeMobile()); + shipping.setContact(contact); + shippingList.add(shipping); + map.put("shipping_list", shippingList); + + + } else if (order.getOrderStatus().equals(OrderStatusEnum.STAY_PICKED_UP.name())) { + + //物流模式,发货方式枚举值:1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品,虚拟商品,例如话费充值,点卡等,无实体配送形式 4、用户自提 + map.put("logistics_type", 4); + //发货模式,发货模式枚举值:1、UNIFIED_DELIVERY(统一发货)2、SPLIT_DELIVERY(分拆发货) 示例值: UNIFIED_DELIVERY + map.put("delivery_mode", 1); + + } + //上传时间,用于标识请求的先后顺序 示例值: `2022-12-15T13:29:35.120+08:00` + map.put("upload_time", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now())); + + //支付者,支付者信息 + Connect connect = connectService.queryConnect(ConnectQueryDTO.builder().userId(UserContext.getCurrentUser().getId()).unionType(SourceEnum.WECHAT_MP_OPEN_ID.name()).build()); + if (connect == null) { + return; + } + map.put("payer", new Payer(connect.getUnionId())); + this.doPostWithJson(url, map); + + + } + + /** + * 是否已开通发货信息管理服务 + * https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html#%E4%B8%83%E3%80%81%E6%9F%A5%E8%AF%A2%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%98%AF%E5%90%A6%E5%B7%B2%E5%BC%80%E9%80%9A%E5%8F%91%E8%B4%A7%E4%BF%A1%E6%81%AF%E7%AE%A1%E7%90%86%E6%9C%8D%E5%8A%A1 + */ + private Boolean isTradeManaged() { + + Setting systemSetting = settingService.get(SettingEnum.WECHAT_PAYMENT.name()); + WechatPaymentSetting wechatPaymentSetting = JSONUtil.toBean(systemSetting.getSettingValue(), WechatPaymentSetting.class); + //发送url + Map map = new HashMap<>(2); + map.put("appid", wechatPaymentSetting.getMpAppId()); + JSONObject jsonObject = this.doPostWithJson(isTradeManagedUrl, map); + + return jsonObject.getBool("is_trade_managed"); + } + + /** + * 查询物流公司编码列表 + * 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 + */ + private String getDeliveryList(Order order) { + //发送url + + Map map = new HashMap<>(2); + JSONObject jsonObject = this.doPostWithJson(DeliveryUrl, map); + Map roomMap = new HashMap<>(2); + List deliveryList = JSONUtil.toList(jsonObject.getStr("delivery_list"), Delivery.class); + for (Delivery delivery : deliveryList) { + if (order.getLogisticsName().equals(delivery.getDelivery_name())) { + return delivery.getDelivery_id(); + } + } + throw new RuntimeException("未找到快递公司"); + } + + + /** + * 请求微信接口 + * + * @param url 链接 + * @param map 参数 + * @return 返回内容 + */ + private JSONObject doPostWithJson(String url, Map map) { + //获取token + String token = wechatAccessTokenUtil.cgiAccessToken(ClientTypeEnum.WECHAT_MP); + //请求链接添加token + url += token; + //发起请求 + String content = HttpUtils.doPostWithJson(url, map); + //记录请求结果 + log.info("微信小程序请求结果:" + content); + //获取请求内容,如果token过期则重新获取,如果出错则抛出错误 + JSONObject jsonObject = new JSONObject(content); + if (("0").equals(jsonObject.get("errcode").toString())) { + return jsonObject; + } else if (("40001").equals(jsonObject.get("errcode"))) { + wechatAccessTokenUtil.removeAccessToken(ClientTypeEnum.WECHAT_MP); + return this.doPostWithJson(url, map); + } else { + throw new ServiceException(jsonObject.get("errmsg").toString()); + } + } + + + @Data + @AllArgsConstructor + @NoArgsConstructor + class Contact { + + /** + * 寄件人联系方式,寄件人联系方式,采用掩码传输,最后4位数字不能打掩码 示例值: `189****1234, 021-****1234, ****1234, 0**2-***1234, 0**2-******23-10, ****123-8008` 值限制: 0 ≤ value ≤ 1024 + */ + String consignor_contact; + + /** + * 收件人联系方式,收件人联系方式为,采用掩码传输,最后4位数字不能打掩码 示例值: `189****1234, 021-****1234, ****1234, 0**2-***1234, 0**2-******23-10, ****123-8008` 值限制: 0 ≤ value ≤ 1024 + */ + String receiver_contact; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + class Delivery { + + private String delivery_id; + private String delivery_name; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + class OrderKey { + + /** + * 订单单号类型,用于确认需要上传详情的订单。枚举值1,使用下单商户号和商户侧单号;枚举值2,使用微信支付单号。 + */ + private Integer order_number_type; + + /** + * 原支付交易对应的微信订单号 + */ + private String transaction_id; + + /** + * 支付下单商户的商户号,由微信支付生成并下发 + */ + private String mchid; + + /** + * 商户系统内部订单号,只能是数字、大小写字母`_-*`且在同一个商户号下唯一 + */ + private String out_trade_no; + + public OrderKey(Order order) { + this.order_number_type = 2; + this.out_trade_no = order.getPayOrderNo(); + this.transaction_id = order.getReceivableNo(); + } + } + + @Data + @NoArgsConstructor + class Payer { + + /** + * 用户标识,用户在小程序appid下的唯一标识。 下单前需获取到用户的Openid 示例值: oUpF8uMuAJO_M2pxb1Q9zNjWeS6o 字符字节限制: [1, 128] + */ + String openid; + + public Payer(String openid) { + this.openid = openid; + } + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + class Shipping { + + /** + * 物流单号,物流快递发货时必填,示例值: 323244567777 字符字节限制: [1, 128] + */ + private String tracking_no; + + /** + * 物流公司编码,快递公司ID,参见「查询物流公司编码列表」,物流快递发货时必填, 示例值: DHL 字符字节限制: [1, 128] + */ + private String express_company; + + /** + * 商品信息,例如:微信红包抱枕*1个,限120个字以内 + */ + private String item_desc; + + /** + * 联系方式,当发货的物流公司为顺丰时,联系方式为必填,收件人或寄件人联系方式二选一 + */ + private Contact contact; + + } + +}