Explorar el Código

同步物流到微信

xdd hace 1 mes
padre
commit
b7ccc92916
Se han modificado 24 ficheros con 1206 adiciones y 1 borrados
  1. 59 0
      fs-admin/src/test/java/com/fs/company/controller/CompanyControllerTest.java
  2. 5 0
      fs-service-system/src/main/java/com/fs/store/service/IFsStoreOrderService.java
  3. 1 0
      fs-service-system/src/main/java/com/fs/store/service/IFsStorePaymentService.java
  4. 70 0
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java
  5. 42 1
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java
  6. 85 0
      fs-service-system/src/main/java/com/fs/wx/domain/FsWxExpressTask.java
  7. 21 0
      fs-service-system/src/main/java/com/fs/wx/dto/Contact.java
  8. 27 0
      fs-service-system/src/main/java/com/fs/wx/dto/OrderKey.java
  9. 80 0
      fs-service-system/src/main/java/com/fs/wx/dto/OrderQueryRequest.java
  10. 236 0
      fs-service-system/src/main/java/com/fs/wx/dto/OrderQueryResponse.java
  11. 17 0
      fs-service-system/src/main/java/com/fs/wx/dto/Payer.java
  12. 26 0
      fs-service-system/src/main/java/com/fs/wx/dto/ShippingItem.java
  13. 38 0
      fs-service-system/src/main/java/com/fs/wx/dto/UploadShippingInfoRequest.java
  14. 21 0
      fs-service-system/src/main/java/com/fs/wx/dto/WeChatApiConfig.java
  15. 20 0
      fs-service-system/src/main/java/com/fs/wx/dto/WeChatApiResponse.java
  16. 96 0
      fs-service-system/src/main/java/com/fs/wx/mapper/FsWxExpressTaskMapper.java
  17. 10 0
      fs-service-system/src/main/java/com/fs/wx/miniapp/config/WxMaProperties.java
  18. 20 0
      fs-service-system/src/main/java/com/fs/wx/service/OrderQueryService.java
  19. 132 0
      fs-service-system/src/main/java/com/fs/wx/service/ShippingService.java
  20. 20 0
      fs-service-system/src/main/java/com/fs/wx/service/WeChatAuthService.java
  21. 105 0
      fs-service-system/src/main/java/com/fs/wx/service/impl/InMemoryWeChatAuthServiceImpl.java
  22. 71 0
      fs-service-system/src/main/java/com/fs/wx/service/impl/OrderQueryServiceImpl.java
  23. 1 0
      fs-service-system/src/main/resources/application-config.yml
  24. 3 0
      fs-service-system/src/main/resources/application.properties

+ 59 - 0
fs-admin/src/test/java/com/fs/company/controller/CompanyControllerTest.java

@@ -7,18 +7,29 @@ import com.fs.erp.domain.ErpOrder;
 import com.fs.erp.domain.ErpOrderItem;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.erp.service.impl.ErpOrderServiceImpl;
+import com.fs.pay.pay.util.PayUtil;
+import com.fs.wx.dto.*;
+import com.fs.wx.service.OrderQueryService;
+import com.fs.wx.service.ShippingService;
+import com.fs.wx.service.WeChatAuthService;
+import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import java.text.SimpleDateFormat;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 
 @RunWith(org.springframework.test.context.junit4.SpringRunner.class)
 @SpringBootTest(classes = FSAdminApplication.class)
+@Slf4j
 public class CompanyControllerTest {
 
     @Autowired
@@ -27,7 +38,55 @@ public class CompanyControllerTest {
     @Autowired
     private IErpOrderService erpOrderService;
 
+    @Autowired
+    private WeChatAuthService weChatAuthService;
+
+    @Autowired
+    private ShippingService shippingService;
+
+    @Test
+    public void test(){
+        UploadShippingInfoRequest request = new UploadShippingInfoRequest();
+        OrderKey orderKey = new OrderKey();
+        orderKey.setTransactionId("4200002594202504182663638650");
+        orderKey.setOrderNumberType(2);
+        request.setOrderKey(orderKey);
+        request.setLogisticsType(1);
+        request.setDeliveryMode(1);
+        request.setShippingList(Arrays.asList(ShippingItem.builder()
+                        .expressCompany("SF")
+                        .trackingNo("SF3172486548114")
+                        .contact(Contact.builder().consignorContact("13021971530").build())
+                .itemDesc("测试商品").build()));
+        OffsetDateTime now = OffsetDateTime.now();
+        DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+        String formattedTimestamp = now.format(formatter);
+        request.setUploadTime(formattedTimestamp);
+        Payer payer = new Payer();
+        payer.setOpenid("obmfC6y5ZOuvq9b4UlL7GUayuYQE");
+        request.setPayer(payer);
+        WeChatApiResponse weChatApiResponse = shippingService.uploadShippingInfo(request);
+
+        log.info("上传物流信息: {} ", weChatApiResponse);
+    }
 
+
+    @Autowired
+    private OrderQueryService orderQueryService;
+    @Test
+    public void test2(){
+        OrderQueryRequest orderQueryRequest = new OrderQueryRequest();
+        orderQueryRequest.setAccount("37519015371852");
+        orderQueryRequest.setUpOrderId("91913098449039003648");
+        orderQueryRequest.setIsNeedUpInfo("1");
+
+        String sign = PayUtil.sign(orderQueryRequest.toSignMap());
+        orderQueryRequest.setSign(sign);
+
+        OrderQueryResponse orderQueryResponse = orderQueryService.queryOrder(orderQueryRequest);
+
+        log.info("获取订单信息: {} ",orderQueryResponse);
+    }
     @Test
     public void addOrder() {
         ErpOrder order=new ErpOrder();

+ 5 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStoreOrderService.java

@@ -27,6 +27,11 @@ import com.fs.store.vo.*;
 public interface IFsStoreOrderService
 {
 
+
+    /**
+     * 同步物流信息到微信
+     */
+    void syncExpressToWx();
     /**
      * 查询订单
      *

+ 1 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStorePaymentService.java

@@ -109,4 +109,5 @@ public interface IFsStorePaymentService
     void delayQueryTzbk(FsStorePayment fsStorePayment);
 
     boolean queryTzbk(FsStorePayment fsStorePayment);
+    boolean queryYb(FsStorePayment fsStorePayment);
 }

+ 70 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java

@@ -7,6 +7,8 @@ import java.sql.Timestamp;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -59,6 +61,7 @@ import com.fs.pay.pay.domain.RefundResult;
 import com.fs.pay.pay.dto.OrderQueryDTO;
 import com.fs.pay.pay.dto.WxJspayDTO;
 import com.fs.pay.pay.service.PayService;
+import com.fs.store.cache.IFsUserCacheService;
 import com.fs.store.cache.impl.IFsStoreProductCacheServiceImpl;
 import com.fs.store.config.StoreConfig;
 import com.fs.store.config.StoreIntegralConfig;
@@ -78,8 +81,13 @@ import com.fs.store.vo.*;
 import com.fs.system.service.ISysConfigService;
 import com.fs.tzBank.TzBankService;
 import com.fs.tzBank.utils.TzConfigUtils;
+import com.fs.wx.domain.FsWxExpressTask;
+import com.fs.wx.dto.*;
+import com.fs.wx.mapper.FsWxExpressTaskMapper;
 import com.fs.wx.miniapp.config.WxMaConfiguration;
 import com.fs.wx.miniapp.config.WxMaProperties;
+import com.fs.wx.service.OrderQueryService;
+import com.fs.wx.service.ShippingService;
 import lombok.Synchronized;
 import me.chanjar.weixin.common.error.WxErrorException;
 import org.apache.commons.collections4.CollectionUtils;
@@ -130,6 +138,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
     @Autowired
     private IFsUserService userService;
 
+    @Autowired
+    private IFsUserCacheService fsUserCacheService;
+
     @Autowired
     private IFsUserBillService billService;
 
@@ -238,6 +249,65 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
 
     @Autowired
     private FsStoreDeliversCacheService fsStoreDeliversCacheService;
+
+    @Autowired
+    private FsWxExpressTaskMapper fsWxExpressTaskMapper;
+
+    @Autowired
+    private ShippingService shippingService;
+
+
+    @Override
+    public void syncExpressToWx() {
+        List<FsWxExpressTask> fsWxExpressTasks = fsWxExpressTaskMapper.selectPendingData();
+        if (CollectionUtils.isEmpty(fsWxExpressTasks)) {
+            logger.info("当前没有待同步的数据!已取消");
+            return;
+        }
+
+        for (FsWxExpressTask fsWxExpressTask : fsWxExpressTasks) {
+
+            UploadShippingInfoRequest request = new UploadShippingInfoRequest();
+
+            OrderKey orderKey = new OrderKey();
+            orderKey.setOrderNumberType(2);
+            orderKey.setTransactionId("1");
+
+
+            FsUser fsUser = fsUserCacheService.selectFsUserById(fsWxExpressTask.getUserId());
+            Payer payer = new Payer();
+            if(StringUtils.isNotBlank(fsUser.getMaOpenId())){
+                payer.setOpenid(fsUser.getMaOpenId());
+            }
+            request.setPayer(payer);
+            request.setOrderKey(orderKey);
+
+            request.setLogisticsType(1);
+            request.setDeliveryMode(1);
+
+            request.setShippingList(Collections.singletonList(ShippingItem.builder().itemDesc("商品").build()));
+
+            OffsetDateTime now = OffsetDateTime.now();
+            DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+            String formattedTimestamp = now.format(formatter);
+            request.setUploadTime(formattedTimestamp);
+
+
+            WeChatApiResponse response = shippingService.uploadShippingInfo(request);
+            if(ObjectUtil.equal(response.getErrcode(),0)){
+                fsWxExpressTask.setStatus(2);
+            } else {
+                fsWxExpressTask.setRetryCount(fsWxExpressTask.getRetryCount() +1);
+                fsWxExpressTask.setStatus(3);
+                fsWxExpressTask.setData(JSON.toJSONString(request));
+                fsWxExpressTask.setRequestBody(JSON.toJSONString(request));
+                fsWxExpressTask.setResponseBody(JSON.toJSONString(response));
+            }
+        }
+        fsWxExpressTaskMapper.batchUpdate(fsWxExpressTasks);
+
+    }
+
     /**
      * 查询订单
      *

+ 42 - 1
fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java

@@ -34,9 +34,13 @@ import com.fs.store.param.FsStorePaymentPayParam;
 import com.fs.store.param.FsStorePaymentParam;
 import com.fs.store.service.IFsUserService;
 import com.fs.store.vo.FsStorePaymentVO;
+import com.fs.wx.dto.OrderQueryRequest;
+import com.fs.wx.dto.OrderQueryResponse;
+import com.fs.wx.service.OrderQueryService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import com.fs.store.mapper.FsStorePaymentMapper;
@@ -75,6 +79,12 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
     @Autowired
     private IFsStoreOrderService orderService;
 
+    @Autowired
+    private OrderQueryService orderQueryService;
+
+    @Value("${wx.miniapp.configs[0].account}")
+    private String account;
+
     /**
      * 查询支付明细
      *
@@ -305,7 +315,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
     public void paymentSync() {
         List<FsStorePayment> fsStorePayments = fsStorePaymentMapper.queryPendingDelayState();
         for (FsStorePayment fsStorePayment : fsStorePayments) {
-            queryTzbk(fsStorePayment);
+            queryYb(fsStorePayment);
         }
     }
 
@@ -368,4 +378,35 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
         }
         return false;
     }
+
+    @Override
+    public boolean queryYb(FsStorePayment fsStorePayment) {
+        // 查询易宝
+
+        OrderQueryRequest orderQueryRequest = new OrderQueryRequest();
+        orderQueryRequest.setAccount(account);
+        orderQueryRequest.setUpOrderId(fsStorePayment.getTradeNo());
+        orderQueryRequest.setIsNeedUpInfo("1");
+
+        OrderQueryResponse orderQueryResponse = orderQueryService.queryOrder(orderQueryRequest);
+        if(ObjectUtil.equal(orderQueryResponse.getStatus(),100)){
+            Long orderId = fsStorePayment.getOrderId();
+
+            // 支付成功
+            if("0".equals(orderQueryResponse.getState())){
+                // 如果查询支付成功 更新订单状态
+                cn.hutool.json.JSONObject upInfo = orderQueryResponse.getUpInfo();
+                String bankOrderId = upInfo.getStr("bankOrderId");
+                orderService.payConfirm(1,orderId, fsStorePayment.getPayCode(),
+                        fsStorePayment.getTradeNo(),bankOrderId, fsStorePayment.getTradeNo());
+                return true;
+                // 1失败 2已撤销
+            } else if("1".equals(orderQueryResponse.getState()) || "2".equals(orderQueryResponse.getState())){
+                fsStorePayment.setStatus(-2);
+                this.updateFsStorePayment(fsStorePayment);
+                return false;
+            }
+        }
+        return false;
+    }
 }

+ 85 - 0
fs-service-system/src/main/java/com/fs/wx/domain/FsWxExpressTask.java

@@ -0,0 +1,85 @@
+package com.fs.wx.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+import java.time.LocalDateTime;
+
+/**
+ * 微信同步发货信息定时任务表
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class FsWxExpressTask {
+
+    /**
+     * 任务ID,唯一标识
+     */
+    private Long id;
+
+    /**
+     * 订单code
+     */
+    private String orderCode;
+
+    /**
+     * 支付单号
+     */
+    private String payCode;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 消息内容,JSON格式。
+     */
+    private String data;
+
+    /**
+     * 任务状态:0=待执行, 1=执行中, 2=执行成功, 3=执行失败, 4=已取消
+     */
+    private Integer status;
+
+    /**
+     * 当前重试次数
+     */
+    private Integer retryCount;
+
+    /**
+     * 最大重试次数
+     */
+    private Integer maxRetries;
+
+    /**
+     * 请求参数(JSON格式,主要记录 access_token 获取方式)
+     */
+    private String requestParams;
+
+    /**
+     * 完整的请求体 (JSON格式)
+     */
+    private String requestBody;
+
+    /**
+     * API 响应结果 (JSON格式)
+     */
+    private String responseBody;
+
+    /**
+     * 错误信息 (如果执行失败)
+     */
+    private String errorMessage;
+
+    /**
+     * 任务创建时间
+     */
+    private LocalDateTime createTime; // 使用LocalDateTime对应timestamp
+
+    /**
+     * 最后更新时间
+     */
+    private LocalDateTime updateTime; // 使用LocalDateTime对应timestamp
+}

+ 21 - 0
fs-service-system/src/main/java/com/fs/wx/dto/Contact.java

@@ -0,0 +1,21 @@
+package com.fs.wx.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Contact {
+    // 根据实际需要添加更多联系人字段
+    @JsonProperty("consignor_contact")
+    private String consignorContact; // 发货人联系方式 (示例字段)
+
+    // 可以添加收货人联系方式等
+    // @JsonProperty("receiver_contact")
+    // private String receiverContact;
+}

+ 27 - 0
fs-service-system/src/main/java/com/fs/wx/dto/OrderKey.java

@@ -0,0 +1,27 @@
+package com.fs.wx.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderKey {
+
+    @JsonProperty("order_number_type")
+    private Integer orderNumberType;
+
+    @JsonProperty("transaction_id")
+    private String transactionId;
+
+    @JsonProperty("mchid")
+    private String mchId;
+
+    @JsonProperty("out_trade_no")
+    private String outTradeNo;
+}

+ 80 - 0
fs-service-system/src/main/java/com/fs/wx/dto/OrderQueryRequest.java

@@ -0,0 +1,80 @@
+package com.fs.wx.dto;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderQueryRequest {
+
+    /**
+     * 聚合账户 (Required)
+     */
+    private String account;
+
+    /**
+     * 下游订单号 (Required if upOrderId is null)
+     */
+    private String lowOrderId;
+
+    /**
+     * 通莞订单号 (Required if lowOrderId is null)
+     */
+    private String upOrderId;
+
+    /**
+     * 值为"Y"时,接口返回优惠信息字段 (Optional)
+     */
+    private String extendInfo;
+
+    /**
+     * 值为"1"时,接口返回易宝专业版分账订单详情 (Optional)
+     */
+    private String isExtend;
+
+    /**
+     * 值为"1"时,接口返回SAAS分账订单详情 (Optional)
+     */
+    private String isNeedUpInfo;
+
+    /**
+     * 值为"1"时,接口返回渠道商户订单号字段 (Optional)
+     */
+    private String isNeedChannelMchOrderId;
+
+    /**
+     * 值为"1"时,返回因公付金额信息,仅支付宝服务商渠道支持 (Optional)
+     */
+    private String isNeedEnterprisePayInfo;
+
+    /**
+     * 签名 (Required)
+     * Note: This will be calculated and set by the service.
+     */
+    private String sign;
+
+    /**
+     * Helper method to get parameters for signing.
+     * Excludes the 'sign' field itself.
+     * Returns sorted map to ensure consistent order for signing.
+     *
+     * @return A map of non-null parameters sorted by key.
+     */
+    public Map<String, String> toSignMap() {
+       Map<String,String> sign = new HashMap<>();
+        sign.put("account",account);
+        sign.put("upOrderId",upOrderId);
+        sign.put("isNeedUpInfo", isNeedUpInfo);
+        return sign;
+    }
+}

+ 236 - 0
fs-service-system/src/main/java/com/fs/wx/dto/OrderQueryResponse.java

@@ -0,0 +1,236 @@
+package com.fs.wx.dto;
+
+import cn.hutool.core.annotation.Alias;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class OrderQueryResponse {
+
+    /**
+     * 100:成功,101:失败 (Required)
+     */
+    private Integer status;
+
+    /**
+     * 消息描述 (Required)
+     */
+    private String message;
+
+    /**
+     * 上游订单号 (Required)
+     */
+    private String channelOrderId;
+
+    /**
+     * 通莞订单号 (Required)
+     */
+    private String upOrderId;
+
+    /**
+     * 默认传null (Required) - Although API doc says required, it seems informational.
+     */
+    private String payoffType;
+
+    /**
+     * 支付时间 (Required)
+     */
+    private String payTime;
+
+    /**
+     * WX: openid; Alipay: account name (Required)
+     * Mapped from both "openId" and "openid" in JSON
+     */
+    @Alias("openid") // Instruct Hutool to map "openid" to this field as well
+    private String openId;
+
+    /**
+     * 签名 (Required) - For response validation
+     */
+    private String sign;
+
+    /**
+     * 结算渠道编号 (Required)
+     */
+    private String settlementChannel;
+
+    /**
+     * 下游订单号 (Required)
+     */
+    private String lowOrderId;
+
+    /**
+     * 支付金额,单位元 (Required)
+     */
+    private String payMoney; // Keep as String as per API doc, can be converted later
+
+    /**
+     * 支付方式 0:WX, 1:ZFB, 2:UnionPay QR, 6:LongPay, 8:BestPay, H:Digital Currency (Required)
+     */
+    private String payType;
+
+    /**
+     * 0:Success, 1:Fail, 2:Revoked, 4:Pending, 5:Refunded, 6:Partial Refund (Required)
+     */
+    private String state;
+
+    /**
+     * 订单备注 (Optional)
+     */
+    private String attach;
+
+    /**
+     * 聚合账户 (Required)
+     */
+    private String account;
+
+    /**
+     * 支付方式例:WX、ZFB、YZF、LZF、YLZF (Required)
+     */
+    private String channelId;
+
+    /**
+     * 渠道优惠金额 JSON String, e.g., {"discountAmt":"100"} (Unit: Fen) (Optional)
+     */
+    private String discountInfo;
+
+    /**
+     * 扩展信息 JSON String (Optional, if requested)
+     * Need to be parsed into an object if needed.
+     */
+    private String extendInfo; // Raw JSON string
+
+    /**
+     * 易宝/SAAS分账详情 JSON Object (Optional, if requested)
+     */
+    private JSONObject extend; // Parsed JSON Object
+
+    /**
+     * SAAS分账订单详情 JSON Object (Optional, if requested)
+     */
+    private JSONObject upInfo; // Parsed JSON Object
+
+    /**
+     * 渠道商户订单号 (Optional, if requested)
+     */
+    private String channelMchOrderId;
+
+    /**
+     * 订单管控状态 FROZEN/UN_FROZEN (Optional, if requested via isNeedUpInfo=1)
+     */
+    private String fundControlCsStatus;
+
+    /**
+     * 管控订单解冻时间 yyyy-mm-dd hh:mm:ss (Optional, if requested via isNeedUpInfo=1 and status is UN_FROZEN)
+     */
+    private String csUnFrozenCompleteDate;
+
+    /**
+     * 因公付金额信息 JSON String, e.g., {"invoiceAmount":"0.01","isUseEnterprisePay":false} (Optional, if requested)
+     */
+    private String enterprisePayInfo; // Raw JSON String
+
+    // --- Helper methods to access parsed nested JSON data ---
+
+    /**
+     * Gets parsed DiscountInfo object from discountInfo string.
+     * @return DiscountInfo object or null if parsing fails or discountInfo is null/empty.
+     */
+    public DiscountInfo getParsedDiscountInfo() {
+        if (JSONUtil.isJson(this.discountInfo)) {
+            try {
+                return JSONUtil.toBean(this.discountInfo, DiscountInfo.class);
+            } catch (Exception e) {
+                // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+     /**
+     * Gets parsed EnterprisePayInfo object from enterprisePayInfo string.
+     * @return EnterprisePayInfo object or null if parsing fails or enterprisePayInfo is null/empty.
+     */
+    public EnterprisePayInfo getParsedEnterprisePayInfo() {
+         if (JSONUtil.isJson(this.enterprisePayInfo)) {
+            try {
+                return JSONUtil.toBean(this.enterprisePayInfo, EnterprisePayInfo.class);
+            } catch (Exception e) {
+                 // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets parsed ExtendInfo object from extendInfo string.
+     * @return JSONObject or null if parsing fails or extendInfo is null/empty.
+     */
+    public JSONObject getParsedExtendInfo() {
+        if (JSONUtil.isJson(this.extendInfo)) {
+            try {
+                return JSONUtil.parseObj(this.extendInfo);
+            } catch (Exception e) {
+                 // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+    // --- Nested DTOs for parsed JSON strings ---
+
+    @Data
+    public static class DiscountInfo {
+        /**
+         * Discount amount in Fen (分)
+         */
+        private String discountAmt; // Keep as String as per API, or use Integer/Long
+
+        public BigDecimal getDiscountAmtYuan() {
+            if (discountAmt != null) {
+                try {
+                    // Assuming discountAmt is in Fen (cents)
+                    return new BigDecimal(discountAmt).divide(new BigDecimal("100"));
+                } catch (NumberFormatException e) {
+                    return null; // Or handle error appropriately
+                }
+            }
+            return null;
+        }
+    }
+
+    @Data
+    public static class EnterprisePayInfo {
+        private String invoiceAmount; // Amount in Yuan (元)
+        private Boolean isUseEnterprisePay;
+
+        public BigDecimal getInvoiceAmountValue() {
+             if (invoiceAmount != null) {
+                 try {
+                     return new BigDecimal(invoiceAmount);
+                 } catch (NumberFormatException e) {
+                     return null; // Or handle error appropriately
+                 }
+             }
+             return null;
+         }
+    }
+
+    /**
+     * Validates the response signature.
+     * IMPORTANT: Implement the actual signature validation logic based on Tongguan's specification.
+     * @param secretKey The secret key.
+     * @return true if the signature is valid, false otherwise.
+     */
+    public boolean isResponseSignValid(String secretKey) {
+
+        System.err.println("WARN: Response signature validation is not implemented!");
+        return true;
+    }
+}

+ 17 - 0
fs-service-system/src/main/java/com/fs/wx/dto/Payer.java

@@ -0,0 +1,17 @@
+package com.fs.wx.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Payer {
+
+    @JsonProperty("openid")
+    private String openid;
+}

+ 26 - 0
fs-service-system/src/main/java/com/fs/wx/dto/ShippingItem.java

@@ -0,0 +1,26 @@
+package com.fs.wx.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShippingItem {
+
+    @JsonProperty("tracking_no")
+    private String trackingNo;
+
+    @JsonProperty("express_company")
+    private String expressCompany;
+
+    @JsonProperty("item_desc")
+    private String itemDesc;
+
+    @JsonProperty("contact")
+    private Contact contact;
+}

+ 38 - 0
fs-service-system/src/main/java/com/fs/wx/dto/UploadShippingInfoRequest.java

@@ -0,0 +1,38 @@
+package com.fs.wx.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UploadShippingInfoRequest {
+
+    @JsonProperty("order_key")
+    private OrderKey orderKey;
+
+    @JsonProperty("logistics_type")
+    private Integer logisticsType;
+
+    @JsonProperty("delivery_mode")
+    private Integer deliveryMode;
+
+    @JsonProperty("is_all_delivered")
+    private Boolean isAllDelivered;
+
+    @JsonProperty("shipping_list")
+    private List<ShippingItem> shippingList;
+
+    @JsonProperty("upload_time")
+    private String uploadTime;
+
+    @JsonProperty("payer")
+    private Payer payer;
+
+}

+ 21 - 0
fs-service-system/src/main/java/com/fs/wx/dto/WeChatApiConfig.java

@@ -0,0 +1,21 @@
+package com.fs.wx.dto;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Getter
+public class WeChatApiConfig {
+
+    @Value("${wechat.api.base-url}")
+    private String baseUrl;
+
+    @Value("${wechat.api.upload-shipping-info}")
+    private String uploadShippingInfoPath;
+
+
+    public String getUploadShippingInfoUrl() {
+        return baseUrl + uploadShippingInfoPath;
+    }
+}

+ 20 - 0
fs-service-system/src/main/java/com/fs/wx/dto/WeChatApiResponse.java

@@ -0,0 +1,20 @@
+package com.fs.wx.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class WeChatApiResponse {
+
+    @JsonProperty("errcode")
+    private Integer errcode;
+
+    @JsonProperty("errmsg")
+    private String errmsg;
+
+    public boolean isSuccess() {
+        return errcode != null && errcode == 0;
+    }
+}

+ 96 - 0
fs-service-system/src/main/java/com/fs/wx/mapper/FsWxExpressTaskMapper.java

@@ -0,0 +1,96 @@
+package com.fs.wx.mapper;
+
+import com.fs.wx.domain.FsWxExpressTask;
+import org.apache.ibatis.annotations.*;
+import java.util.List;
+
+/**
+ * 微信同步发货信息定时任务表 Mapper 接口
+ */
+@Mapper
+public interface FsWxExpressTaskMapper {
+
+    /**
+     * 根据ID查询任务
+     * @param id 任务ID
+     * @return FsWxExpressTask 任务实体
+     */
+    @Select("SELECT * FROM fs_wx_express_task WHERE id = #{id}")
+    FsWxExpressTask selectById(@Param("id") Long id);
+
+    /**
+     * 插入新任务
+     * @param task 任务实体
+     * @return 影响行数
+     */
+    @Insert("INSERT INTO fs_wx_express_task (order_code, user_id, data, status, retry_count, max_retries, request_params, request_body, response_body, error_message, create_time, update_time) " +
+            "VALUES (#{orderCode}, #{userId}, #{data}::json, #{status}, #{retryCount}, #{maxRetries}, #{requestParams}, #{requestBody}, #{responseBody}, #{errorMessage}, #{createTime}, #{updateTime})")
+    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
+    int insert(FsWxExpressTask task);
+
+    /**
+     * 根据ID更新任务信息
+     * @param task 任务实体
+     * @return 影响行数
+     */
+    @Update("<script>" +
+            "UPDATE fs_wx_express_task " +
+            "<set>" +
+            "  <if test='orderCode != null'>order_code = #{orderCode},</if>" +
+            "  <if test='userId != null'>user_id = #{userId},</if>" +
+            "  <if test='data != null'>data = #{data}::json,</if>" +
+            "  <if test='status != null'>status = #{status},</if>" +
+            "  <if test='retryCount != null'>retry_count = #{retryCount},</if>" +
+            "  <if test='maxRetries != null'>max_retries = #{maxRetries},</if>" +
+            "  <if test='requestParams != null'>request_params = #{requestParams},</if>" +
+            "  <if test='requestBody != null'>request_body = #{requestBody},</if>" +
+            "  <if test='responseBody != null'>response_body = #{responseBody},</if>" +
+            "  <if test='errorMessage != null'>error_message = #{errorMessage},</if>" +
+            "</set>" +
+            "WHERE id = #{id}" +
+            "</script>")
+    int updateById(FsWxExpressTask task);
+
+    /**
+     * 根据ID删除任务
+     * @param id 任务ID
+     * @return 影响行数
+     */
+    @Delete("DELETE FROM fs_wx_express_task WHERE id = #{id}")
+    int deleteById(@Param("id") Long id);
+
+    /**
+     * 根据状态查询任务列表
+     * @param status 任务状态
+     * @return List<FsWxExpressTask> 任务列表
+     */
+    @Select("SELECT * FROM fs_wx_express_task WHERE status = #{status}")
+    List<FsWxExpressTask> selectByStatus(@Param("status") Integer status);
+
+
+    /**
+     * 查询待处理数据
+     * @return
+     */
+    @Select("SELECT * FROM fs_wx_express_task WHERE retry_count < 3 AND status in (0,3)")
+    List<FsWxExpressTask> selectPendingData();
+    @Update("<script>" +
+            "<foreach collection='list' item='task' separator=';'>" +
+            " UPDATE fs_wx_express_task" +
+            " SET" +
+            "  order_code = #{task.orderCode}," +
+            "  user_id = #{task.userId}," +
+            "  data = #{task.data}," +
+            "  status = #{task.status}," +
+            "  retry_count = #{task.retryCount}," +
+            "  max_retries = #{task.maxRetries}," +
+            "  request_params = #{task.requestParams}," +
+            "  request_body = #{task.requestBody}," +
+            "  response_body = #{task.responseBody}," +
+            "  error_message = #{task.errorMessage}," +
+            "  update_time = now()" +
+            " WHERE id = #{task.id}" +
+            "</foreach>" +
+            "</script>")
+    void batchUpdate(List<FsWxExpressTask> fsWxExpressTasks);
+}

+ 10 - 0
fs-service-system/src/main/java/com/fs/wx/miniapp/config/WxMaProperties.java

@@ -44,6 +44,8 @@ public class WxMaProperties {
          */
         private String msgDataFormat;
 
+        private String account;
+
         public String getAppid() {
             return appid;
         }
@@ -83,6 +85,14 @@ public class WxMaProperties {
         public void setMsgDataFormat(String msgDataFormat) {
             this.msgDataFormat = msgDataFormat;
         }
+
+        public String getAccount() {
+            return account;
+        }
+
+        public void setAccount(String account) {
+            this.account = account;
+        }
     }
 
 }

+ 20 - 0
fs-service-system/src/main/java/com/fs/wx/service/OrderQueryService.java

@@ -0,0 +1,20 @@
+package com.fs.wx.service;
+
+
+import com.fs.wx.dto.OrderQueryRequest;
+import com.fs.wx.dto.OrderQueryResponse;
+
+public interface OrderQueryService {
+
+    /**
+     * Queries an order using either lowOrderId or upOrderId.
+     *
+     * @param request The request object containing query parameters.
+     *                The 'sign' field will be calculated internally.
+     * @return The order query response.
+     * @throws IllegalArgumentException if required parameters are missing.
+     * @throws RuntimeException if the API call fails or returns an error status.
+     */
+    OrderQueryResponse queryOrder(OrderQueryRequest request);
+
+}

+ 132 - 0
fs-service-system/src/main/java/com/fs/wx/service/ShippingService.java

@@ -0,0 +1,132 @@
+package com.fs.wx.service;
+
+import cn.hutool.http.ContentType;
+import cn.hutool.http.Header;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONUtil;
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.http.HttpException;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.serializer.SerializeConfig;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fs.wx.dto.UploadShippingInfoRequest;
+import com.fs.wx.dto.WeChatApiConfig;
+import com.fs.wx.dto.WeChatApiResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@Service
+public class ShippingService {
+
+    private static final Logger log = LoggerFactory.getLogger(ShippingService.class);
+
+    private final WeChatApiConfig weChatApiConfig;
+    private final WeChatAuthService weChatAuthService;
+
+    public ShippingService(WeChatApiConfig weChatApiConfig, WeChatAuthService weChatAuthService) {
+        this.weChatApiConfig = weChatApiConfig;
+        this.weChatAuthService = weChatAuthService;
+    }
+
+    /**
+     * 调用微信 API 上传发货信息 (使用 Hutool HttpUtil)
+     * @param request 发货信息请求体
+     * @return 微信 API 的响应
+     */
+    public WeChatApiResponse uploadShippingInfo(UploadShippingInfoRequest request) {
+        String accessToken = weChatAuthService.getAccessToken();
+        if (accessToken == null) {
+            log.error("获取微信 Access Token 失败");
+            WeChatApiResponse errorResponse = new WeChatApiResponse();
+            errorResponse.setErrcode(-3); // 自定义错误码,表示获取Token失败
+            errorResponse.setErrmsg("获取微信 Access Token 失败");
+            return errorResponse;
+        }
+
+        String url = UriComponentsBuilder.fromHttpUrl(weChatApiConfig.getUploadShippingInfoUrl())
+                .queryParam("access_token", accessToken)
+                .toUriString();
+
+        log.debug("请求微信上传发货接口 URL: {}", url);
+
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        String requestBodyJson = null;
+        try {
+            requestBodyJson = objectMapper.writeValueAsString(request);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+
+        log.debug("请求体 JSON: {}", requestBodyJson);
+
+
+        HttpResponse httpResponse = null;
+        try {
+            HttpRequest httpRequest = HttpUtil.createPost(url)
+                    .header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
+                    .body(requestBodyJson)
+                    .timeout(10000);
+
+            httpResponse = httpRequest.execute();
+
+            int statusCode = httpResponse.getStatus();
+            String responseBodyString = httpResponse.body();
+
+            log.info("微信接口响应状态: {}", statusCode);
+            log.info("微信接口响应体: {}", responseBodyString);
+
+            if (httpResponse.isOk()) {
+                WeChatApiResponse weChatApiResponse = JSONUtil.toBean(responseBodyString, WeChatApiResponse.class);
+
+                if (!weChatApiResponse.isSuccess()) {
+                    log.warn("微信接口返回业务错误: code={}, message={}", weChatApiResponse.getErrcode(), weChatApiResponse.getErrmsg());
+                }
+                return weChatApiResponse;
+
+            } else {
+                log.error("调用微信接口收到非成功状态码: {}", statusCode);
+                try {
+                    WeChatApiResponse errorResponse = JSONUtil.toBean(responseBodyString, WeChatApiResponse.class);
+                    if (errorResponse.getErrcode() == null) {
+                        errorResponse.setErrcode(statusCode);
+                        errorResponse.setErrmsg("微信接口HTTP错误,状态码: " + statusCode + ", 响应体: " + responseBodyString);
+                    }
+                    return errorResponse;
+                } catch (Exception parseEx) {
+                    log.warn("无法将微信错误响应体解析为JSON: {}", responseBodyString, parseEx);
+                    WeChatApiResponse errorResponse = new WeChatApiResponse();
+                    errorResponse.setErrcode(statusCode);
+                    errorResponse.setErrmsg("调用微信接口失败,状态码: " + statusCode + ", 原始响应体: " + responseBodyString);
+                    return errorResponse;
+                }
+            }
+        } catch (HttpException e) {
+            log.error("调用微信接口发生HTTP异常: {}", e.getMessage(), e);
+            WeChatApiResponse errorResponse = new WeChatApiResponse();
+            errorResponse.setErrcode(-1);
+            String detailedMessage = ExceptionUtil.getMessage(e);
+            errorResponse.setErrmsg("调用微信接口时发生HTTP异常: " + detailedMessage);
+            if (httpResponse != null) {
+                errorResponse.setErrmsg(errorResponse.getErrmsg() + ", HTTP状态码: " + httpResponse.getStatus());
+            }
+            return errorResponse;
+        } catch (Exception e) {
+            log.error("调用微信接口时发生意外错误", e);
+            WeChatApiResponse errorResponse = new WeChatApiResponse();
+            errorResponse.setErrcode(-2);
+            errorResponse.setErrmsg("调用微信接口时发生内部服务器错误: " + e.getMessage());
+            return errorResponse;
+        }
+    }
+}

+ 20 - 0
fs-service-system/src/main/java/com/fs/wx/service/WeChatAuthService.java

@@ -0,0 +1,20 @@
+package com.fs.wx.service;
+
+public interface WeChatAuthService {
+    /**
+     * 获取有效的微信小程序 Access Token
+     * @param forceRefresh 是否强制刷新,忽略缓存
+     * @return Access Token
+     * @throws RuntimeException 如果获取失败
+     */
+    String getAccessToken(boolean forceRefresh);
+
+    /**
+     * 获取有效的微信小程序 Access Token (优先使用缓存)
+     * @return Access Token
+     * @throws RuntimeException 如果获取失败
+     */
+    default String getAccessToken() {
+        return getAccessToken(true);
+    }
+}

+ 105 - 0
fs-service-system/src/main/java/com/fs/wx/service/impl/InMemoryWeChatAuthServiceImpl.java

@@ -0,0 +1,105 @@
+package com.fs.wx.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.fs.wx.service.WeChatAuthService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class InMemoryWeChatAuthServiceImpl implements WeChatAuthService {
+
+    private static final Logger log = LoggerFactory.getLogger(InMemoryWeChatAuthServiceImpl.class);
+    private String cachedToken = null;
+    private long expiryTime = 0; // Token 过期时间戳 (ms)
+
+    @Value("${wx.miniapp.configs[0].appid}")
+    private String appId;
+
+    @Value("${wx.miniapp.configs[0].secret}")
+    private String appSecret;
+
+    private final String tokenUrlFormat = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
+
+    public InMemoryWeChatAuthServiceImpl() {
+    }
+
+
+    @Override
+    public synchronized String getAccessToken(boolean forceRefresh) {
+        long now = System.currentTimeMillis();
+        if (!forceRefresh && cachedToken != null && now < expiryTime) {
+            log.debug("Using cached access token.");
+            return cachedToken;
+        }
+
+        if (StrUtil.hasBlank(appId, appSecret)) {
+            log.error("appId或者appSecret不存在!");
+            throw new RuntimeException("appId或者appSecret不存在!");
+        }
+
+
+        String url = String.format(tokenUrlFormat, appId, appSecret);
+        log.info(url);
+        HttpResponse httpResponse = null;
+        String body = null;
+
+        try {
+            httpResponse = HttpRequest.get(url)
+                    .timeout(5000)
+                    .execute();
+
+            body = httpResponse.body();
+
+            if (!httpResponse.isOk()) {
+                log.error("获取accessToken失败!. Status: {}, Body: {}", httpResponse.getStatus(), body);
+                String errorMsg = parseErrorMsg(body);
+                throw new RuntimeException("获取accessToken失败! Status: " + httpResponse.getStatus() + ", Message: " + errorMsg);
+            }
+
+            if (StrUtil.isBlank(body)) {
+                log.error("获取accessToken失败!.  URL: {}", url);
+                throw new RuntimeException("获取accessToken失败!");
+            }
+
+            JSONObject responseJson = JSONUtil.parseObj(body);
+
+            if (responseJson.containsKey("access_token")) {
+                cachedToken = responseJson.getStr("access_token");
+                if(StrUtil.isBlank(cachedToken)){
+                    log.error("获取accessToken失败!response: {}", body);
+                    throw new RuntimeException("获取accessToken失败!");
+                }
+
+                long expiresInSeconds = responseJson.getLong("expires_in", 7200L);
+                expiryTime = now + (expiresInSeconds - 300) * 1000;
+                log.info("获取accessToken获取成功 {}",cachedToken);
+                return cachedToken;
+            } else {
+                String errorMsg = responseJson.getStr("errmsg", "Unknown error: access_token missing in response");
+                log.error("Failed to fetch access token, 'access_token' key missing. Response: {}", body);
+                throw new RuntimeException("Failed to fetch access token: " + errorMsg);
+            }
+        } catch (Exception e) {
+            log.error("Error fetching access token from URL: {}", url, e);
+            throw new RuntimeException("Error fetching access token: " + e.getMessage(), e);
+        }
+    }
+
+    private String parseErrorMsg(String jsonBody) {
+        if (StrUtil.isBlank(jsonBody)) {
+            return "返回为空!";
+        }
+        try {
+            JSONObject json = JSONUtil.parseObj(jsonBody);
+            return json.getStr("errmsg", "Unknown error in response body");
+        } catch (Exception e) {
+            return "返回解析失败!";
+        }
+    }
+}

+ 71 - 0
fs-service-system/src/main/java/com/fs/wx/service/impl/OrderQueryServiceImpl.java

@@ -0,0 +1,71 @@
+package com.fs.wx.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.Header;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import com.fs.wx.dto.OrderQueryRequest;
+import com.fs.wx.dto.OrderQueryResponse;
+import com.fs.wx.service.OrderQueryService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class OrderQueryServiceImpl implements OrderQueryService {
+
+    private static final Logger log = LoggerFactory.getLogger(OrderQueryServiceImpl.class);
+
+    private String apiBaseUrl = "https://tgpay.833006.net";
+
+    private String secretKey = "YOUR_SECRET_KEY";
+
+    private static final String ORDER_QUERY_PATH = "/tgPosp/services/payApi/orderQuery";
+    private static final int SUCCESS_STATUS = 100;
+
+    @Override
+    public OrderQueryResponse queryOrder(OrderQueryRequest request) {
+        String apiUrl = apiBaseUrl + ORDER_QUERY_PATH;
+        String requestBody = JSONUtil.toJsonStr(request);
+
+        log.info("Sending Order Query request to {}: {}", apiUrl, requestBody);
+
+        HttpResponse response = null;
+        try {
+            response = HttpRequest.post(apiUrl)
+                    .header(Header.CONTENT_TYPE, "application/json;charset=utf-8")
+                    .body(requestBody)
+                    .timeout(10000)
+                    .execute();
+
+            String responseBody = response.body();
+            log.info("Received Order Query response ({}): {}", response.getStatus(), responseBody);
+
+            if (!response.isOk()) {
+                throw new RuntimeException(String.format("API request failed with HTTP status %d: %s",
+                        response.getStatus(), responseBody));
+            }
+
+            if (StrUtil.isBlank(responseBody)) {
+                throw new RuntimeException("API response body is empty.");
+            }
+
+            OrderQueryResponse queryResponse = JSONUtil.toBean(responseBody, OrderQueryResponse.class, true); // Use lenient parsing for openId/openid
+
+
+            if (queryResponse.getStatus() == null || queryResponse.getStatus() != SUCCESS_STATUS) {
+                 log.error("API returned failure status [{}]: {}", queryResponse.getStatus(), queryResponse.getMessage());
+                 throw new RuntimeException(String.format("API error [%d]: %s",
+                         queryResponse.getStatus() != null ? queryResponse.getStatus() : -1,
+                         queryResponse.getMessage()));
+            }
+
+            return queryResponse;
+
+        } catch (Exception e) {
+            log.error("Error during Order Query API call", e);
+            throw new RuntimeException("Failed to query order: " + e.getMessage(), e);
+        }
+    }
+}

+ 1 - 0
fs-service-system/src/main/resources/application-config.yml

@@ -69,6 +69,7 @@ wx:
         token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
         aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
         msgDataFormat: JSON
+        account: 37519015371852
   pay:
     appId: wx19c8813ffc33d1cb #微信公众号或者小程序等的appid
     mchId: 1560230251 #微信支付商户号

+ 3 - 0
fs-service-system/src/main/resources/application.properties

@@ -0,0 +1,3 @@
+# application.properties
+wechat.api.base-url=https://api.weixin.qq.com
+wechat.api.upload-shipping-info=/wxa/sec/order/upload_shipping_info