xiangpei
2025-06-04 434ea8cef0d23ee20b6047631ef4afa371b43eb3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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<String, Object> 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<Shipping> shippingList = new ArrayList<>();
            Shipping shipping = new Shipping();
            shipping.setTracking_no(order.getLogisticsNo());
            shipping.setExpress_company(getDeliveryList(order));
            //商品信息,例如:微信红包抱枕*1个,限120个字以内
            List<OrderItem> 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<String, Object> 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<String, Object> map = new HashMap<>(2);
        JSONObject jsonObject = this.doPostWithJson(DeliveryUrl, map);
        Map<String, String> roomMap = new HashMap<>(2);
        List<Delivery> 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;
 
    }
 
}