소스 검색

对接erp

xdd 2 달 전
부모
커밋
79136b625e
22개의 변경된 파일1976개의 추가작업 그리고 7개의 파일을 삭제
  1. 1 0
      fs-admin/src/main/java/com/fs/store/controller/FsStoreOrderController.java
  2. 6 6
      fs-admin/src/main/resources/application-dev.yml
  3. 172 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/api/WdtClient.java
  4. 39 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/DeliveryTerm.java
  5. 36 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/FenxiaoType.java
  6. 41 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/PaymentStatus.java
  7. 46 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/RefundStatus.java
  8. 69 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/TradeStatus.java
  9. 146 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/utils/FileItem.java
  10. 129 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/utils/StringUtils.java
  11. 562 0
      fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/utils/WebUtils.java
  12. 93 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiRefund.java
  13. 40 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiResponse.java
  14. 31 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtBusinessRequestParams.java
  15. 36 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtCommonRequestParams.java
  16. 99 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtOrder.java
  17. 19 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtRefundBusinessRequestParams.java
  18. 25 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtRefundOrder.java
  19. 193 0
      fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtTrade.java
  20. 186 0
      fs-service-system/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java
  21. 1 1
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java
  22. 6 0
      fs-service-system/src/main/resources/application-config.yml

+ 1 - 0
fs-admin/src/main/java/com/fs/store/controller/FsStoreOrderController.java

@@ -532,6 +532,7 @@ public class FsStoreOrderController extends BaseController {
     @GetMapping("/createErpOrder")
     public R createErpOrder(@RequestParam("orderCode") String orderCode) throws Exception
     {
+        logger.info("手动推管易订单号:{}",orderCode);
         FsStoreOrder order=fsStoreOrderService.selectFsStoreOrderByOrderCode(orderCode);
         fsStoreOrderService.createOmsOrder(order.getId());
         return R.ok();

+ 6 - 6
fs-admin/src/main/resources/application-dev.yml

@@ -28,9 +28,9 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://139.186.77.83:3306/ylrz_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: Rtroot
-                    password: Rtroot
+                    url: jdbc:mysql://localhost:3306/ylrz_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: 123456
                 # 从库数据源
                 slave:
                     # 从数据源开关/默认关闭
@@ -83,9 +83,9 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://139.186.77.83/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: Rtroot
-                    password: Rtroot
+                    url: jdbc:mysql://localhost/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: 123456
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 172 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/api/WdtClient.java

@@ -0,0 +1,172 @@
+package com.fs.erp.dto.sdk.wangdian.api;
+
+
+import com.alibaba.fastjson.TypeReference;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.erp.dto.sdk.wangdian.utils.WebUtils;
+import com.fs.erp.dto.wdt.ErpWdtBusinessRequestParams;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Wdt客户端
+ */
+@Component
+@Slf4j
+public class WdtClient {
+
+	@Value("${fsConfig.erpWdAppKey}")
+	private static String appkey;
+	@Value("${fsConfig.erpWdAppsecret}")
+	private static String appsecret;
+	@Value("${fsConfig.erpWdSid}")
+	private static String sid;
+	@Value("${fsConfig.erpWdBaseUrl}")
+	private static String baseUrl;
+
+	private static WdtClient wdtClient;
+
+
+	private int connectTimeout = 3000;//3秒
+	private int readTimeout = 15000;//15秒
+
+	@Value("${fsConfig.erpWdAppKey}")
+	public void setAppkey(String appkey) {
+		WdtClient.appkey = appkey;
+	}
+	@Value("${fsConfig.erpWdAppsecret}")
+	public void setAppsecret(String appsecret) {
+		WdtClient.appsecret = appsecret;
+	}
+	@Value("${fsConfig.erpWdSid}")
+	public void setSid(String sid) {
+		WdtClient.sid = sid;
+	}
+	@Value("${fsConfig.erpWdBaseUrl}")
+	public void setBaseUrl(String baseUrl) {
+		WdtClient.baseUrl = baseUrl;
+	}
+
+	private WdtClient(){}
+
+	public static WdtClient getInstance(){
+		if(wdtClient == null){
+			synchronized (WdtClient.class){
+				if(wdtClient == null){
+					wdtClient = new WdtClient();
+				}
+			}
+		}
+		return wdtClient;
+	}
+
+	private static String getStringFromException(Throwable e) {
+		String result = "";
+		ByteArrayOutputStream bos = new ByteArrayOutputStream();
+		PrintStream ps = new PrintStream(bos);
+		e.printStackTrace(ps);
+		try {
+			result = bos.toString("UTF-8");
+		} catch (IOException ioe) {
+		}
+		return result;
+	}
+
+	private static byte[] encryptMD5(String data) throws IOException {
+		byte[] bytes = null;
+		try {
+			MessageDigest md = MessageDigest.getInstance("MD5");
+			bytes = md.digest(data.getBytes("UTF-8"));
+		} catch (GeneralSecurityException gse) {
+			String msg = getStringFromException(gse);
+			throw new IOException(msg);
+		}
+		return bytes;
+	}
+
+	private static String byte2hex(byte[] bytes) {
+		StringBuilder sign = new StringBuilder();
+		for (int i = 0; i < bytes.length; i++) {
+			String hex = Integer.toHexString(bytes[i] & 0xFF);
+			if (hex.length() == 1) {
+				//保证所有的16进制都是两位:00-ff,其中[80~ff]代表[-128,-1]
+				sign.append("0");
+			}
+			sign.append(hex);
+		}
+		return sign.toString();
+	}
+
+	/**
+	 * 给TOP请求签名。
+	 *
+	 * @param params 所有字符型的TOP请求参数
+	 * @param appsecret 签名密钥
+	 * @return 签名
+	 * @throws IOException
+	 */
+	public static String signRequest(Map<String, String> params, String appsecret) throws IOException {
+		// 第一步:检查参数是否已经排序
+		String[] keys = params.keySet().toArray(new String[0]);
+		Arrays.sort(keys);
+
+		// 第二步:把所有参数名和参数值串在一起
+		StringBuilder query = new StringBuilder();
+		for (String key : keys) {
+			if("sign".equals(key))
+				continue;
+
+			if(query.length() > 0)
+				query.append(';');
+
+			int len = key.length();
+			query.append(String.format("%02d", len)).append('-').append(key).append(':');
+
+			String value = params.get(key);
+
+			len = value.length();
+			query.append(String.format("%04d", len)).append('-').append(value);
+
+		}
+
+		query.append(appsecret);
+
+		// 第三步:使用MD5加密
+		byte[] bytes = encryptMD5(query.toString());
+
+		// 第四步:把二进制转化为大写的十六进制
+		return byte2hex(bytes);
+	}
+
+	public String execute(String relativeUrl, Map<String, String> params) throws IOException {
+		log.info("开始执行请求,相对路径:{},请求参数:{}", relativeUrl, params);
+
+
+		params.put("appkey", appkey);
+		params.put("sid", sid);
+		params.put("timestamp", Long.toString(System.currentTimeMillis()/1000));
+
+		String sign = signRequest(params, appsecret);
+		params.put("sign", sign);
+		log.info("计算签名完成, 签名为:{}",sign);
+		String url = baseUrl + relativeUrl;
+		long startTime = System.currentTimeMillis();
+		String response = WebUtils.doPost(url, params, "UTF-8", connectTimeout, readTimeout, null);
+		long endTime = System.currentTimeMillis();
+		long costTime = endTime - startTime;
+		// 打印 HTTP 请求的详细信息
+		log.info("请求URL: {}, 请求体: {}, 请求头: {}, 响应体: {}, 耗时: {} ms", url, params, null, response, costTime);
+		return response;
+	}
+}

+ 39 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/DeliveryTerm.java

@@ -0,0 +1,39 @@
+package com.fs.erp.dto.sdk.wangdian.enums;
+
+public enum DeliveryTerm {
+    PAYMENT_BEFORE_DELIVERY(1, "款到发货"),
+    CASH_ON_DELIVERY(2, "货到付款(包含部分货到付款)"),
+    INSTALLMENT_PAYMENT(3, "分期付款"),
+    CREDIT_ACCOUNT(4, "挂账");
+
+    private final int value;
+    private final String description;
+
+    DeliveryTerm(int value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 根据状态值获取枚举实例
+     *
+     * @param value 状态值
+     * @return 对应的枚举实例,如果找不到则返回 null
+     */
+    public static DeliveryTerm fromValue(int value) {
+        for (DeliveryTerm term : DeliveryTerm.values()) {
+            if (term.value == value) {
+                return term;
+            }
+        }
+        return null;
+    }
+}

+ 36 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/FenxiaoType.java

@@ -0,0 +1,36 @@
+package com.fs.erp.dto.sdk.wangdian.enums;
+
+public enum FenxiaoType {
+    DAIXIAO(1, "代销"),
+    JINGXIAO(2, "经销");
+
+    private final int value;
+    private final String description;
+
+    FenxiaoType(int value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 根据类型值获取枚举实例
+     * @param value 类型值
+     * @return 对应的枚举实例, 如果找不到则返回 null
+     */
+     public static FenxiaoType fromValue(int value){
+         for(FenxiaoType type : FenxiaoType.values()){
+             if(type.value == value){
+                 return  type;
+             }
+         }
+         return null;
+     }
+}

+ 41 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/PaymentStatus.java

@@ -0,0 +1,41 @@
+package com.fs.erp.dto.sdk.wangdian.enums;
+
+/**
+ * 支付状态枚举类
+ */
+public enum PaymentStatus {
+    UNPAID(0, "未付款"),
+    PARTIALLY_PAID(1, "部分付款"),
+    FULLY_PAID(2, "已付款");
+
+    private final int value;
+    private final String description;
+
+    PaymentStatus(int value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+     /**
+     * 根据状态值获取枚举实例
+     *
+     * @param value 状态值
+     * @return 对应的枚举实例,如果找不到则返回 null
+     */
+    public static PaymentStatus fromValue(int value) {
+        for (PaymentStatus status : PaymentStatus.values()) {
+            if (status.value == value) {
+                return status;
+            }
+        }
+        return null;
+    }
+}

+ 46 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/RefundStatus.java

@@ -0,0 +1,46 @@
+package com.fs.erp.dto.sdk.wangdian.enums;
+
+public enum RefundStatus {
+    NO_REFUND(0, "无退款"),
+    CANCELED_REFUND(1, "取消退款"),
+    REFUND_APPLIED(2, "已申请退款"),
+    WAITING_FOR_RETURN(3, "等待退货"),
+    WAITING_FOR_RECEIPT(4, "等待收货"),
+    REFUND_SUCCESSFUL(5, "退款成功");
+
+    private final int value;
+    private final String description;
+
+    RefundStatus(int value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 根据状态值获取枚举实例
+     *
+     * @param value 状态值
+     * @return 对应的枚举实例,如果找不到则返回 null
+     */
+    public static RefundStatus fromValue(int value) {
+        for (RefundStatus status : RefundStatus.values()) {
+            if (status.value == value) {
+                return status;
+            }
+        }
+        return null;
+    }
+
+    // 可选:添加售前退款场景的特定方法(如果需要更精细的控制)
+    // 例如,可以根据具体的售前退款场景,提供一个方法来获取对应的 RefundStatus
+    // public static RefundStatus fromPreSaleScenario(PreSaleRefundScenario scenario) { ... }
+
+}

+ 69 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/enums/TradeStatus.java

@@ -0,0 +1,69 @@
+package com.fs.erp.dto.sdk.wangdian.enums;
+
+public enum TradeStatus {
+
+    UNCONFIRMED(10, "未确认"),
+    WAITING_FOR_FINAL_PAYMENT(20, "待尾款"),
+    PAID_WAITING_FOR_SHIPMENT(30, "已付款待发货"),
+    PARTIALLY_SHIPPED(40, "部分发货"),
+    SHIPPED(50, "已发货"),
+    COMPLETED(70, "已完成"),
+    REFUNDED(80, "已退款"),
+    CLOSED(90, "已关闭");
+
+    private final int value;
+    private final String description;
+
+    TradeStatus(int value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 检查状态是否可以更新到新的状态
+     * @param newStatus 新的状态
+     * @return 是否可以更新
+     */
+    public boolean canUpdateTo(TradeStatus newStatus) {
+        // 状态值仅限从小向大更新,不支持逆向更新
+        if (this.value >= newStatus.value) {
+            return false;
+        }
+
+        // 特殊规则:30 只能变更为 70 或 80
+        if (this == PAID_WAITING_FOR_SHIPMENT &&
+            newStatus != COMPLETED && newStatus != REFUNDED) {
+            return false;
+        }
+
+        //已发货状态,在自建商城可以直接到完成状态70
+        if(this == SHIPPED && newStatus == COMPLETED){
+            return  true;
+        }
+
+        return true;
+    }
+
+     /**
+     * 根据状态值获取枚举实例
+     *
+     * @param value 状态值
+     * @return 对应的枚举实例,如果找不到则返回 null
+     */
+    public static TradeStatus fromValue(int value) {
+        for (TradeStatus status : TradeStatus.values()) {
+            if (status.value == value) {
+                return status;
+            }
+        }
+        return null;
+    }
+}

+ 146 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/utils/FileItem.java

@@ -0,0 +1,146 @@
+package com.fs.erp.dto.sdk.wangdian.utils;
+
+import java.io.*;
+
+
+/**
+ * 文件元数据。
+ *
+ */
+public class FileItem {
+
+	private String fileName;
+	private String mimeType;
+	private byte[] content;
+	private File file;
+
+	/**
+	 * 基于本地文件的构造器。
+	 *
+	 * @param file 本地文件
+	 */
+	public FileItem(File file) {
+		this.file = file;
+	}
+
+	/**
+	 * 基于文件绝对路径的构造器。
+	 *
+	 * @param filePath 文件绝对路径
+	 */
+	public FileItem(String filePath) {
+		this(new File(filePath));
+	}
+
+	/**
+	 * 基于文件名和字节流的构造器。
+	 *
+	 * @param fileName 文件名
+	 * @param content 文件字节流
+	 */
+	public FileItem(String fileName, byte[] content) {
+		this.fileName = fileName;
+		this.content = content;
+	}
+
+	/**
+	 * 基于文件名、字节流和媒体类型的构造器。
+	 *
+	 * @param fileName 文件名
+	 * @param content 文件字节流
+	 * @param mimeType 媒体类型
+	 */
+	public FileItem(String fileName, byte[] content, String mimeType) {
+		this(fileName, content);
+		this.mimeType = mimeType;
+	}
+
+	public String getFileName() {
+		if (this.fileName == null && this.file != null && this.file.exists()) {
+			this.fileName = file.getName();
+		}
+		return this.fileName;
+	}
+
+	public String getMimeType() throws IOException {
+		if (this.mimeType == null) {
+			this.mimeType = getMimeType(getContent());
+		}
+		return this.mimeType;
+	}
+
+	/**
+	 * 获取文件的真实后缀名。目前只支持JPG, GIF, PNG, BMP四种图片文件。
+	 *
+	 * @param bytes 文件字节流
+	 * @return JPG, GIF, PNG or null
+	 */
+	public static String getFileSuffix(byte[] bytes) {
+		if (bytes == null || bytes.length < 10) {
+			return null;
+		}
+
+		if (bytes[0] == 'G' && bytes[1] == 'I' && bytes[2] == 'F') {
+			return "GIF";
+		} else if (bytes[1] == 'P' && bytes[2] == 'N' && bytes[3] == 'G') {
+			return "PNG";
+		} else if (bytes[6] == 'J' && bytes[7] == 'F' && bytes[8] == 'I' && bytes[9] == 'F') {
+			return "JPG";
+		} else if (bytes[0] == 'B' && bytes[1] == 'M') {
+			return "BMP";
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * 获取文件的真实媒体类型。目前只支持JPG, GIF, PNG, BMP四种图片文件。
+	 *
+	 * @param bytes 文件字节流
+	 * @return 媒体类型(MEME-TYPE)
+	 */
+	public static String getMimeType(byte[] bytes) {
+		String suffix = getFileSuffix(bytes);
+		String mimeType;
+
+		if ("JPG".equals(suffix)) {
+			mimeType = "image/jpeg";
+		} else if ("GIF".equals(suffix)) {
+			mimeType = "image/gif";
+		} else if ("PNG".equals(suffix)) {
+			mimeType = "image/png";
+		} else if ("BMP".equals(suffix)) {
+			mimeType = "image/bmp";
+		}else {
+			mimeType = "application/octet-stream";
+		}
+
+		return mimeType;
+	}
+
+	public byte[] getContent() throws IOException {
+		if (this.content == null && this.file != null && this.file.exists()) {
+			InputStream in = null;
+			ByteArrayOutputStream out = null;
+
+			try {
+				in = new FileInputStream(this.file);
+				out = new ByteArrayOutputStream();
+				int ch;
+				while ((ch = in.read()) != -1) {
+					out.write(ch);
+				}
+				this.content = out.toByteArray();
+			} finally {
+				if (out != null) {
+					out.close();
+				}
+				if (in != null) {
+					in.close();
+				}
+			}
+		}
+		return this.content;
+	}
+
+}

+ 129 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/utils/StringUtils.java

@@ -0,0 +1,129 @@
+package com.fs.erp.dto.sdk.wangdian.utils;
+
+
+/**
+ * 字符串工具类。
+ *
+ */
+public abstract class StringUtils {
+
+	private StringUtils() {}
+
+	/**
+	 * 检查指定的字符串是否为空。
+	 * <ul>
+	 * <li>SysUtils.isEmpty(null) = true</li>
+	 * <li>SysUtils.isEmpty("") = true</li>
+	 * <li>SysUtils.isEmpty("   ") = true</li>
+	 * <li>SysUtils.isEmpty("abc") = false</li>
+	 * </ul>
+	 *
+	 * @param value 待检查的字符串
+	 * @return true/false
+	 */
+	public static boolean isEmpty(String value) {
+		int strLen;
+		if (value == null || (strLen = value.length()) == 0) {
+			return true;
+		}
+		for (int i = 0; i < strLen; i++) {
+			if ((Character.isWhitespace(value.charAt(i)) == false)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * 检查对象是否为数字型字符串,包含负数开头的。
+	 */
+	public static boolean isNumeric(Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		char[] chars = obj.toString().toCharArray();
+		int length = chars.length;
+		if(length < 1)
+			return false;
+
+		int i = 0;
+		if(length > 1 && chars[0] == '-')
+			i = 1;
+
+		for (; i < length; i++) {
+			if (!Character.isDigit(chars[i])) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * 检查指定的字符串列表是否不为空。
+	 */
+	public static boolean areNotEmpty(String... values) {
+		boolean result = true;
+		if (values == null || values.length == 0) {
+			result = false;
+		} else {
+			for (String value : values) {
+				result &= !isEmpty(value);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * 把通用字符编码的字符串转化为汉字编码。
+	 */
+	public static String unicodeToChinese(String unicode) {
+		StringBuilder out = new StringBuilder();
+		if (!isEmpty(unicode)) {
+			for (int i = 0; i < unicode.length(); i++) {
+				out.append(unicode.charAt(i));
+			}
+		}
+		return out.toString();
+	}
+
+	public static String toUnderlineStyle(String name) {
+		StringBuilder newName = new StringBuilder();
+		for (int i = 0; i < name.length(); i++) {
+			char c = name.charAt(i);
+			if (Character.isUpperCase(c)) {
+				if (i > 0) {
+					newName.append("_");
+				}
+				newName.append(Character.toLowerCase(c));
+			} else {
+				newName.append(c);
+			}
+		}
+		return newName.toString();
+	}
+
+	public static String convertString(byte[] data, int offset, int length) {
+		if (data == null) {
+			return null;
+		} else {
+			try {
+				return new String(data, offset, length, "UTF-8");
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			}
+		}
+	}
+
+	public static byte[] convertBytes(String data) {
+		if (data == null) {
+			return null;
+		} else {
+			try {
+				return data.getBytes("UTF-8");
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			}
+		}
+	}
+
+}

+ 562 - 0
fs-service-system/src/main/java/com/fs/erp/dto/sdk/wangdian/utils/WebUtils.java

@@ -0,0 +1,562 @@
+package com.fs.erp.dto.sdk.wangdian.utils;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+//import javax.net.ssl.HostnameVerifier;
+//import javax.net.ssl.HttpsURLConnection;
+//import javax.net.ssl.SSLContext;
+//import javax.net.ssl.SSLSession;
+//import javax.net.ssl.TrustManager;
+//import javax.net.ssl.X509TrustManager;
+
+
+/**
+ * 网络工具类。
+ *
+ */
+public abstract class WebUtils {
+
+	private static final String DEFAULT_CHARSET = "UTF-8";
+	private static final String METHOD_POST = "POST";
+	private static final String METHOD_GET = "GET";
+	//private static final Certificate verisign; // 根证书
+	//private static boolean ignoreSSLCheck; // 忽略SSL检查
+
+//	static {
+//		InputStream input = null;
+//		try {
+//			CertificateFactory cf = CertificateFactory.getInstance("X.509");
+//			input = WebUtils.class.getResourceAsStream("/verisign.crt");
+//			verisign = cf.generateCertificate(input);
+//		} catch (Exception e) {
+//			throw new RuntimeException(e);
+//		} finally {
+//			if (input != null) {
+//				try {
+//					input.close();
+//				} catch (IOException e) {
+//				}
+//			}
+//		}
+//	}
+
+//	public static class VerisignTrustManager implements X509TrustManager {
+//		public X509Certificate[] getAcceptedIssuers() {
+//			return null;
+//		}
+//
+//		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+//		}
+//
+//		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+//			Exception exp = null;
+//
+//			for (X509Certificate cert : chain) {
+//				cert.checkValidity(); // 验证证书有效期
+//				try {
+//					cert.verify(verisign.getPublicKey());// 验证签名
+//					exp = null;
+//					break;
+//				} catch (Exception e) {
+//					exp = e;
+//				}
+//			}
+//
+//			if (exp != null) {
+//				throw new CertificateException(exp);
+//			}
+//		}
+//	}
+//
+//	public static class TrustAllTrustManager implements X509TrustManager {
+//		public X509Certificate[] getAcceptedIssuers() {
+//			return null;
+//		}
+//
+//		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+//		}
+//
+//		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+//		}
+//	}
+
+	private WebUtils() {
+	}
+
+//	public static void setIgnoreSSLCheck(boolean ignoreSSLCheck) {
+//		WebUtils.ignoreSSLCheck = ignoreSSLCheck;
+//	}
+
+	/**
+	 * 执行HTTP POST请求。
+	 *
+	 * @param url 请求地址
+	 * @param params 请求参数
+	 * @return 响应字符串
+	 */
+	public static String doPost(String url, Map<String, String> params, int connectTimeout, int readTimeout) throws IOException {
+		return doPost(url, params, DEFAULT_CHARSET, connectTimeout, readTimeout);
+	}
+
+	/**
+	 * 执行HTTP POST请求。
+	 *
+	 * @param url 请求地址
+	 * @param params 请求参数
+	 * @param charset 字符集,如UTF-8, GBK, GB2312
+	 * @return 响应字符串
+	 */
+	public static String doPost(String url, Map<String, String> params, String charset, int connectTimeout, int readTimeout) throws IOException {
+		return doPost(url, params, charset, connectTimeout, readTimeout, null);
+	}
+
+	public static String doPost(String url, Map<String, String> params, String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {
+		String ctype = "application/x-www-form-urlencoded;charset=" + charset;
+		String query = buildQuery(params, charset);
+		byte[] content = {};
+		if (query != null) {
+			content = query.getBytes(charset);
+		}
+		return _doPost(url, ctype, content, connectTimeout, readTimeout, headerMap);
+	}
+
+	/**
+	 * 执行HTTP POST请求。
+	 *
+	 * @param url 请求地址
+	 * @param ctype 请求类型
+	 * @param content 请求字节数组
+	 * @return 响应字符串
+	 */
+	public static String doPost(String url, String ctype, byte[] content, int connectTimeout, int readTimeout) throws IOException {
+		return _doPost(url, ctype, content, connectTimeout, readTimeout, null);
+	}
+
+	private static String _doPost(String url, String ctype, byte[] content, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {
+		HttpURLConnection conn = null;
+		OutputStream out = null;
+		String rsp = null;
+		try {
+			conn = getConnection(new URL(url), METHOD_POST, ctype, headerMap);
+			conn.setConnectTimeout(connectTimeout);
+			conn.setReadTimeout(readTimeout);
+
+			out = conn.getOutputStream();
+			out.write(content);
+
+			rsp = getResponseAsString(conn);
+		} finally {
+			if (out != null) {
+				out.close();
+			}
+			if (conn != null) {
+				conn.disconnect();
+			}
+		}
+
+		return rsp;
+	}
+
+	/**
+	 * 执行带文件上传的HTTP POST请求。
+	 *
+	 * @param url 请求地址
+	 * @param textParams 文本请求参数
+	 * @param fileParams 文件请求参数
+	 * @return 响应字符串
+	 */
+	public static String doPost(String url, Map<String, String> params, Map<String, FileItem> fileParams, int connectTimeout, int readTimeout) throws IOException {
+		if (fileParams == null || fileParams.isEmpty()) {
+			return doPost(url, params, DEFAULT_CHARSET, connectTimeout, readTimeout);
+		} else {
+			return doPost(url, params, fileParams, DEFAULT_CHARSET, connectTimeout, readTimeout);
+		}
+	}
+
+	public static String doPost(String url, Map<String, String> params, Map<String, FileItem> fileParams, String charset, int connectTimeout, int readTimeout) throws IOException {
+		return doPost(url, params, fileParams, charset, connectTimeout, readTimeout, null);
+	}
+
+	/**
+	 * 执行带文件上传的HTTP POST请求。
+	 *
+	 * @param url 请求地址
+	 * @param textParams 文本请求参数
+	 * @param fileParams 文件请求参数
+	 * @param charset 字符集,如UTF-8, GBK, GB2312
+	 * @param headerMap 需要传递的header头,可以为空
+	 * @return 响应字符串
+	 */
+	public static String doPost(String url, Map<String, String> params, Map<String, FileItem> fileParams, String charset,
+			int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {
+		if (fileParams == null || fileParams.isEmpty()) {
+			return doPost(url, params, charset, connectTimeout, readTimeout, headerMap);
+		} else {
+			return _doPostWithFile(url, params, fileParams, charset, connectTimeout, readTimeout, headerMap);
+		}
+	}
+
+	private static String _doPostWithFile(String url, Map<String, String> params, Map<String, FileItem> fileParams,
+			String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {
+		String boundary = String.valueOf(System.nanoTime()); // 随机分隔线
+		HttpURLConnection conn = null;
+		OutputStream out = null;
+		String rsp = null;
+		try {
+			String ctype = "multipart/form-data;charset=" + charset + ";boundary=" + boundary;
+			conn = getConnection(new URL(url), METHOD_POST, ctype, headerMap);
+			conn.setConnectTimeout(connectTimeout);
+			conn.setReadTimeout(readTimeout);
+
+			out = conn.getOutputStream();
+			byte[] entryBoundaryBytes = ("\r\n--" + boundary + "\r\n").getBytes(charset);
+
+			// 组装文本请求参数
+			Set<Entry<String, String>> textEntrySet = params.entrySet();
+			for (Entry<String, String> textEntry : textEntrySet) {
+				byte[] textBytes = getTextEntry(textEntry.getKey(), textEntry.getValue(), charset);
+				out.write(entryBoundaryBytes);
+				out.write(textBytes);
+			}
+
+			// 组装文件请求参数
+			Set<Entry<String, FileItem>> fileEntrySet = fileParams.entrySet();
+			for (Entry<String, FileItem> fileEntry : fileEntrySet) {
+				FileItem fileItem = fileEntry.getValue();
+				if (fileItem.getContent() == null) {
+					continue;
+				}
+				byte[] fileBytes = getFileEntry(fileEntry.getKey(), fileItem.getFileName(), fileItem.getMimeType(), charset);
+				out.write(entryBoundaryBytes);
+				out.write(fileBytes);
+				out.write(fileItem.getContent());
+			}
+
+			// 添加请求结束标志
+			byte[] endBoundaryBytes = ("\r\n--" + boundary + "--\r\n").getBytes(charset);
+			out.write(endBoundaryBytes);
+			rsp = getResponseAsString(conn);
+
+		} finally {
+			if (out != null) {
+				out.close();
+			}
+			if (conn != null) {
+				conn.disconnect();
+			}
+		}
+
+		return rsp;
+	}
+
+	private static byte[] getTextEntry(String fieldName, String fieldValue, String charset) throws IOException {
+		StringBuilder entry = new StringBuilder();
+		entry.append("Content-Disposition:form-data;name=\"");
+		entry.append(fieldName);
+		entry.append("\"\r\nContent-Type:text/plain\r\n\r\n");
+		entry.append(fieldValue);
+		return entry.toString().getBytes(charset);
+	}
+
+	private static byte[] getFileEntry(String fieldName, String fileName, String mimeType, String charset) throws IOException {
+		StringBuilder entry = new StringBuilder();
+		entry.append("Content-Disposition:form-data;name=\"");
+		entry.append(fieldName);
+		entry.append("\";filename=\"");
+		entry.append(fileName);
+		entry.append("\"\r\nContent-Type:");
+		entry.append(mimeType);
+		entry.append("\r\n\r\n");
+		return entry.toString().getBytes(charset);
+	}
+
+	/**
+	 * 执行HTTP GET请求。
+	 *
+	 * @param url 请求地址
+	 * @param params 请求参数
+	 * @return 响应字符串
+	 */
+	public static String doGet(String url, Map<String, String> params) throws IOException {
+		return doGet(url, params, DEFAULT_CHARSET);
+	}
+
+	/**
+	 * 执行HTTP GET请求。
+	 *
+	 * @param url 请求地址
+	 * @param params 请求参数
+	 * @param charset 字符集,如UTF-8, GBK, GB2312
+	 * @return 响应字符串
+	 */
+	public static String doGet(String url, Map<String, String> params, String charset) throws IOException {
+		HttpURLConnection conn = null;
+		String rsp = null;
+
+		try {
+			String ctype = "application/x-www-form-urlencoded;charset=" + charset;
+			String query = buildQuery(params, charset);
+
+			conn = getConnection(buildGetUrl(url, query), METHOD_GET, ctype, null);
+
+			rsp = getResponseAsString(conn);
+
+		} finally {
+			if (conn != null) {
+				conn.disconnect();
+			}
+		}
+
+		return rsp;
+	}
+
+	private static HttpURLConnection getConnection(URL url, String method, String ctype, Map<String, String> headerMap) throws IOException {
+		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+//		if (conn instanceof HttpsURLConnection) {
+//			HttpsURLConnection connHttps = (HttpsURLConnection) conn;
+//			if (ignoreSSLCheck) {
+//				try {
+//					SSLContext ctx = SSLContext.getInstance("TLS");
+//					ctx.init(null, new TrustManager[] { new TrustAllTrustManager() }, new SecureRandom());
+//					connHttps.setSSLSocketFactory(ctx.getSocketFactory());
+//					connHttps.setHostnameVerifier(new HostnameVerifier() {
+//						public boolean verify(String hostname, SSLSession session) {
+//							return true;
+//						}
+//					});
+//				} catch (Exception e) {
+//					throw new IOException(e);
+//				}
+//			} else {
+//				try {
+//					SSLContext ctx = SSLContext.getInstance("TLS");
+//					ctx.init(null, new TrustManager[] { new VerisignTrustManager() }, new SecureRandom());
+//					connHttps.setSSLSocketFactory(ctx.getSocketFactory());
+//				} catch (Exception e) {
+//					throw new IOException(e);
+//				}
+//			}
+//			conn = connHttps;
+//		}
+
+		conn.setRequestMethod(method);
+		conn.setDoInput(true);
+		conn.setDoOutput(true);
+		conn.setRequestProperty("Accept", "*/*");
+		conn.setRequestProperty("User-Agent", "wdt-java-sdk");
+		conn.setRequestProperty("Content-Type", ctype);
+		if (headerMap != null) {
+			for (Entry<String, String> entry : headerMap.entrySet()) {
+				conn.setRequestProperty(entry.getKey(), entry.getValue());
+			}
+		}
+		return conn;
+	}
+
+	private static URL buildGetUrl(String strUrl, String query) throws IOException {
+		URL url = new URL(strUrl);
+		if (StringUtils.isEmpty(query)) {
+			return url;
+		}
+
+		if (StringUtils.isEmpty(url.getQuery())) {
+			if (strUrl.endsWith("?")) {
+				strUrl = strUrl + query;
+			} else {
+				strUrl = strUrl + "?" + query;
+			}
+		} else {
+			if (strUrl.endsWith("&")) {
+				strUrl = strUrl + query;
+			} else {
+				strUrl = strUrl + "&" + query;
+			}
+		}
+
+		return new URL(strUrl);
+	}
+
+	public static String buildQuery(Map<String, String> params, String charset) throws IOException {
+		if (params == null || params.isEmpty()) {
+			return null;
+		}
+
+		StringBuilder query = new StringBuilder();
+		Set<Entry<String, String>> entries = params.entrySet();
+		boolean hasParam = false;
+
+		for (Entry<String, String> entry : entries) {
+			String name = entry.getKey();
+			String value = entry.getValue();
+			// 忽略参数名或参数值为空的参数
+			if (StringUtils.areNotEmpty(name, value)) {
+				if (hasParam) {
+					query.append("&");
+				} else {
+					hasParam = true;
+				}
+
+				query.append(name).append("=").append(URLEncoder.encode(value, charset));
+			}
+		}
+
+		return query.toString();
+	}
+
+	protected static String getResponseAsString(HttpURLConnection conn) throws IOException {
+		String charset = getResponseCharset(conn.getContentType());
+		InputStream es = conn.getErrorStream();
+		if (es == null) {
+			return getStreamAsString(conn.getInputStream(), charset);
+		} else {
+			String msg = getStreamAsString(es, charset);
+			if (StringUtils.isEmpty(msg)) {
+				throw new IOException(conn.getResponseCode() + ":" + conn.getResponseMessage());
+			} else {
+				throw new IOException(msg);
+			}
+		}
+	}
+
+	private static String getStreamAsString(InputStream stream, String charset) throws IOException {
+		try {
+			Reader reader = new InputStreamReader(stream, charset);
+			StringBuilder response = new StringBuilder();
+
+			final char[] buff = new char[1024];
+			int read = 0;
+			while ((read = reader.read(buff)) > 0) {
+				// 一次读取1024个字符
+				response.append(buff, 0, read);
+			}
+
+			return response.toString();
+		} finally {
+			if (stream != null) {
+				stream.close();
+			}
+		}
+	}
+
+	private static String getResponseCharset(String ctype) {
+		String charset = DEFAULT_CHARSET;
+
+		if (!StringUtils.isEmpty(ctype)) {
+			String[] params = ctype.split(";");
+			for (String param : params) {
+				param = param.trim();
+				if (param.startsWith("charset")) {
+					String[] pair = param.split("=", 2);
+					if (pair.length == 2) {
+						if (!StringUtils.isEmpty(pair[1])) {
+							charset = pair[1].trim();
+						}
+					}
+					break;
+				}
+			}
+		}
+
+		return charset;
+	}
+
+	/**
+	 * 使用默认的UTF-8字符集反编码请求参数值。
+	 *
+	 * @param value 参数值
+	 * @return 反编码后的参数值
+	 */
+	public static String decode(String value) {
+		return decode(value, DEFAULT_CHARSET);
+	}
+
+	/**
+	 * 使用默认的UTF-8字符集编码请求参数值。
+	 *
+	 * @param value 参数值
+	 * @return 编码后的参数值
+	 */
+	public static String encode(String value) {
+		return encode(value, DEFAULT_CHARSET);
+	}
+
+	/**
+	 * 使用指定的字符集反编码请求参数值。
+	 *
+	 * @param value 参数值
+	 * @param charset 字符集
+	 * @return 反编码后的参数值
+	 */
+	public static String decode(String value, String charset) {
+		String result = null;
+		if (!StringUtils.isEmpty(value)) {
+			try {
+				result = URLDecoder.decode(value, charset);
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * 使用指定的字符集编码请求参数值。
+	 *
+	 * @param value 参数值
+	 * @param charset 字符集
+	 * @return 编码后的参数值
+	 */
+	public static String encode(String value, String charset) {
+		String result = null;
+		if (!StringUtils.isEmpty(value)) {
+			try {
+				result = URLEncoder.encode(value, charset);
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		return result;
+	}
+
+	public static Map<String, String> getParamsFromUrl(String url) {
+		Map<String, String> map = null;
+		if (url != null && url.indexOf('?') != -1) {
+			map = splitUrlQuery(url.substring(url.indexOf('?') + 1));
+		}
+		if (map == null) {
+			map = new HashMap<String, String>();
+		}
+		return map;
+	}
+
+	/**
+	 * 从URL中提取所有的参数。
+	 *
+	 * @param query URL地址
+	 * @return 参数映射
+	 */
+	public static Map<String, String> splitUrlQuery(String query) {
+		Map<String, String> result = new HashMap<String, String>();
+
+		String[] pairs = query.split("&");
+		if (pairs != null && pairs.length > 0) {
+			for (String pair : pairs) {
+				String[] param = pair.split("=", 2);
+				if (param != null && param.length == 2) {
+					result.put(param[0], param[1]);
+				}
+			}
+		}
+
+		return result;
+	}
+
+}

+ 93 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiRefund.java

@@ -0,0 +1,93 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 旺店通开放平台 API 退款单信息 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtApiRefund {
+
+    /**
+     * 平台ID
+     * <p>输入固定值127</p>
+     */
+    private short platformId;
+
+    /**
+     * 店铺编号
+     * <p>代表店铺所有属性的唯一编码,用于店铺区分,ERP内支持自定义(ERP店铺界面设置),用于推送指定店铺原始退款单据信息</p>
+     */
+    private String shopNo;
+
+    /**
+     * 平台销售订单号
+     * <p>平台订单单号(与推送trade_push的tid保持一致,对应旺店通ERP内原始单号)</p>
+     */
+    private String tid;
+
+    /**
+     * 平台退款/退货单号
+     * <p>平台退款/退货单单号,保证唯一</p>
+     */
+    private String refundNo;
+
+    /**
+     * 平台售后类型
+     * <p>type=1:退款(未发货退款);type=2:退款不退货;type=3:退货</p>
+     */
+    private Integer type;
+
+    /**
+     * 平台售后状态
+     * <p>可选值:wait_seller_agree(申请退款),  seller_refuse(拒绝退款) , closed(退款关闭) ,goods_returning(待退货) goods_receiving(待收货),success(退款成功)</p>
+     */
+    private String status;
+
+    /**
+     * 退款金额
+     */
+    private BigDecimal refundFee;
+
+    /**
+     * 买家昵称
+     * <p>买家昵称(不传该字段,默认取原始订单上客户网名)</p>
+     */
+    private String buyerNick;
+
+    /**
+     * 单据创建时间
+     * <p>单据创建时间,时间格式:yyyy-MM-dd HH:mm:ss</p>
+     */
+    private String refundTime;
+
+    /**
+     * 退款原因
+     */
+    private String reason;
+
+    /**
+     * 备注
+     */
+    private String desc;
+
+    /**
+     * 物流单号
+     * <p>物流单号(如需维护退货物流单号,与logistics_name物流公司名称同时使用)</p>
+     */
+    private String logisticsNo;
+
+    /**
+     * 物流公司名称
+     */
+    private String logisticsName;
+
+    /**
+     * 售后子订单
+     */
+    private List<ErpWdtRefundOrder> orderList;
+}

+ 40 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiResponse.java

@@ -0,0 +1,40 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+import org.apache.commons.lang3.StringEscapeUtils;
+
+/**
+ * 旺店通开放平台 API 响应 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtApiResponse {
+
+    /**
+     * 错误码
+     * <p>状态码:0表示成功,其他表示失败</p>
+     */
+    private Integer code;
+
+    /**
+     * 错误原因
+     */
+    private String message;
+
+    public String getMessage() {
+        return StringEscapeUtils.unescapeJava(message);
+    }
+
+    /**
+     * 返回的新增订单个数
+     * <p>有单据新增时不为0(新建的原始单据)</p>
+     */
+    private Integer newCount;
+
+    /**
+     * 返回的更新订单个数
+     * <p>有单据更新时不为0(在已有的原始单据上进行修改)</p>
+     */
+    private Integer chgCount;
+}

+ 31 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtBusinessRequestParams.java

@@ -0,0 +1,31 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+import java.util.List;
+
+/**
+ * 旺店通开放平台 API 业务请求参数 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtBusinessRequestParams {
+
+    /**
+     * 店铺编号
+     * <p>代表店铺所有属性的唯一编码,用于店铺区分,ERP内支持自定义(ERP店铺界面设置,查看路径ERP→设置→基本设置→店铺→店铺列表),用于创建指定店铺单据信息,测试环境店铺编号信息同接口账号。ERP内shop_no对应的“店铺平台”必须为“自有/其它”。正式环境店铺创建成功后ERP会自动授权,如果自动授权失败,在ERP店铺界面(路径ERP→设置→基本设置→店铺→店铺列表)选中授权失败的店铺,点击店铺界面提供的店铺授权按钮重新授权。</p>
+     */
+    private String shopNo;
+
+    /**
+     * 模式
+     * <p>0:非严格模式,1:严格模式,默认1。严格模式详情介绍单击这里</p>
+     */
+    private Integer switchMode;
+
+    /**
+     * 订单列表节点
+     * <p>请求参数的1级数据节点,包含销售订单所有属性信息的数据节点,节点下数据字段详见下述“trade_list”</p>
+     */
+    private List<ErpWdtTrade> tradeList;
+}

+ 36 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtCommonRequestParams.java

@@ -0,0 +1,36 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+
+/**
+ * 旺店通开放平台 API 公共请求参数 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtCommonRequestParams {
+
+    /**
+     * 卖家账号
+     * <p>购买ERP时由旺店通分配给ERP购买方,请从ERP购买方获取。</p>
+     */
+    private String sid;
+
+    /**
+     * 接口账号
+     * <p>本开放平台“自助对接”功能模块内自助申请,获取方式点击这里</p>
+     */
+    private String appkey;
+
+    /**
+     * 时间戳
+     * <p>北京时间1970-01-01 08:00:00起至现在的总秒数,10位int值,旺店通企业版API服务端允许请求最大时间误差为5min,date.timezone = Asia/Shanghai。</p>
+     */
+    private int timestamp;
+
+    /**
+     * 签名
+     * <p>API输入参数签名结果,签名算法介绍单击这里</p>
+     */
+    private String sign;
+}

+ 99 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtOrder.java

@@ -0,0 +1,99 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+import java.math.BigDecimal;
+
+/**
+ * 旺店通开放平台 API 订单货品明细 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtOrder {
+
+    /**
+     * 子订单编号
+     * <p>平台订单货品表主键,子订单唯一标识,同一个sid下通过本接口新增订单的oid(子订单编号)要保证唯一;如果oid重复,ERP生成系统单(递交)时会提示“订单货品数量不一致xxxxxx”</p>
+     */
+    private String oid;
+
+    /**
+     * 数量
+     * <p>货品数量,订单推送成功以后本字段值不能更改</p>
+     */
+    private BigDecimal num;
+
+    /**
+     * 单价
+     * <p>标价,折扣前的价格,可以推送价格为0的商品。订单推送成功以后本字段值不能更改</p>
+     */
+    private BigDecimal price;
+
+    /**
+     * 状态
+     * <p>平台子订单状态,子订单状态可以和主订单不一样,比如其中一个子订单退款完成,其状态是80,但主订单仍然是待发货,可选值同trade_status</p>
+     */
+    private Integer status;
+
+    /**
+     * 退款状态
+     * <p>0:无退款,1:取消退款,2:已申请退款,3:等待退货,4:等待收货,5:退款成功。本字段在售前退款的时候,根据不同的场景填写不同的值,eg:申请退款值为2,取消退款值为1……</p>
+     */
+    private Integer refundStatus;
+
+    /**
+     * 平台货品ID
+     * <p>平台系统货品(SPU)的唯一标识。goods_id不能为空,goods_id和goods_no区别与SPU、SKU概念介绍,单击这里</p>
+     */
+    private String goodsId;
+
+    /**
+     * 平台规格ID
+     * <p>平台系统单品(SKU)的的唯一标识,尽量不为空,spec_id和spec_no区别与SPU、SKU概念介绍,单击这里</p>
+     */
+    private String specId;
+
+    /**
+     * 货品编码
+     * <p>平台货品SPU编码,对应ERP货品编号,尽量不为空</p>
+     */
+    private String goodsNo;
+
+    /**
+     * 规格编码
+     * <p>平台货品SKU唯一码,对应ERP商家编码,goods_no和spec_no不能同时为空</p>
+     */
+    private String specNo;
+
+    /**
+     * 货品名称
+     */
+    private String goodsName;
+
+    /**
+     * 规格名称
+     */
+    private String specName;
+
+    /**
+     * 调整
+     * <p>客服调整总金额(大于0加价,小于0减价,是折扣来源的一部分,没有传0)</p>
+     */
+    private BigDecimal adjustAmount;
+
+    /**
+     * 优惠
+     * <p>下单总折扣,客户下单时折扣(比如促销打折,不包含客服调整、分摊折扣,没有传0)</p>
+     */
+    private BigDecimal discount;
+
+    /**
+     * 分摊优惠
+     * <p>分摊总折扣,由总订单分摊而来,一般是付款时产生,如使用优惠券,没有传0。分摊优惠传值注意:例如三个商品,优惠10,分摊优惠可以是:3/3/4,或者3.33/3.33/3.34.即最后一个商品的分摊优惠使用减法计算</p>
+     */
+    private BigDecimal shareDiscount;
+
+    private BigDecimal commission;
+    private String remark;
+    private String cid;
+}

+ 19 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtRefundBusinessRequestParams.java

@@ -0,0 +1,19 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+import java.util.List;
+
+/**
+ * 旺店通开放平台 API 退款/售后业务请求参数 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtRefundBusinessRequestParams {
+
+    /**
+     * 退款单节点
+     * <p>请求参数的1级数据节点,包含平台售后订单所有属性信息的数据节点,节点下数据字段详见下述“api_refund_list”</p>
+     */
+    private List<ErpWdtApiRefund> apiRefundList;
+}

+ 25 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtRefundOrder.java

@@ -0,0 +1,25 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+import java.math.BigDecimal;
+
+/**
+ * 旺店通开放平台 API 售后子订单 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtRefundOrder {
+
+    /**
+     * 平台订单子订单编号
+     * <p>平台订单子订单编号(与推送trade_push的oid保持一致,对应旺店通ERP内原始子订单编号)可推送部分子订单</p>
+     */
+    private String oid;
+
+    /**
+     * 退货货品数量
+     * <p>退货货品数量(如果是type=1 退款   2 退款不退货类型,此处代表退款数量)</p>
+     */
+    private BigDecimal num;
+}

+ 193 - 0
fs-service-system/src/main/java/com/fs/erp/dto/wdt/ErpWdtTrade.java

@@ -0,0 +1,193 @@
+package com.fs.erp.dto.wdt;
+
+import lombok.Data;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 旺店通开放平台 API 订单信息 DTO
+ * @author xdd
+ * @date 2025-02-27
+ */
+@Data
+public class ErpWdtTrade {
+
+    /**
+     * 原始单号
+     * <p>指商城、官网等平台的订单编号,ERP称之为原始单号,同一个sid下通过本接口新增订单的tid保证唯一。</p>
+     */
+    private String tid;
+
+    /**
+     * 平台状态
+     * <p>平台订单状态较多且涉及变更,详情介绍单击这里</p>
+     */
+    private Integer tradeStatus;
+
+    /**
+     * 支付状态
+     * <p>平台订单付款状态:0:未付款,1:部分付款,2:已付款</p>
+     */
+    private Integer payStatus;
+
+    /**
+     * 发货条件
+     * <p>1:款到发货,2:货到付款(包含部分货到付款),3:分期付款,4:挂账</p>
+     */
+    private Integer deliveryTerm;
+
+    /**
+     * 下单时间
+     * <p>平台订单创建时间,时间格式:yyyy-MM-dd HH:mm:ss</p>
+     */
+    private String tradeTime;
+
+    /**
+     * 支付时间
+     * <p>平台订单付款时间,时间格式:yyyy-MM-dd HH:mm:ss,未付款订单为:0000-00-00 00:00:00</p>
+     */
+    private String payTime;
+
+    /**
+     * 分销类别
+     */
+    private Integer fenxiaoType;
+    /**
+     * 采购单ID
+     */
+    private String purchaseId;
+
+    private  String fenxiaoNick;
+    /**
+     * 	客户网名
+     */
+    private String buyerNick;
+
+    private String buyerEmail;
+
+    private String payId;
+    private String payAccount;
+    /**
+     * 支付方式:  1在线支付 2现金,3银行转账,4邮局汇款 5预付款 6刷卡 7支付宝 8微信支付  不传默认为1 在线支付
+     */
+    private Integer payMethod;
+    private String receiverName;
+    private String receiverProvince;
+    private String receiverCity;
+    private String receiverDistrict;
+    private String receiverAddress;
+    private String receiverMobile;
+    private String receiverTelno;
+    private String receiverZip;
+    /**
+     * 物流方式
+     * <p>不传本参数,输入值默认-1,表示由ERP系统策略选择。平台指定订单发货物流可选输入值单击这里</p>
+     */
+    private Integer logisticsType;
+
+    /**
+     * 发票类别
+     * <p>0:不需要,1:普通发票,2:增值税普通发票电子,3:增值税普通发票纸质,4:增值税专用发票</p>
+     */
+    private Integer invoiceKind;
+
+    /**
+     * 发票抬头
+     */
+    private String invoiceTitle;
+
+    /**
+     * 发票内容
+     * <p>常见内容:纳税人识别号、地址、电话、开户银行、银行账户,按照以下格式推送,系统开发票时可解析。</p>
+     * <p>推送格式:纳税人识别号:xxxxxxxxxxx;地址:xxxxxxxx 13888888888;开户银行:银行名称 银行账户;</p>
+     * <p>英文分号以后是地址,地址后面是空格,空格后面是电话,电话后面是英文分号,分号以后是开户行,然后空格,然后是银行账号</p>
+     */
+    private String invoiceContent;
+    /**
+     * 客户备注标旗,取值0至5对应的标旗颜色依次为灰(无标旗)、红、黄、绿、蓝、紫,不传默认0
+     */
+    private Integer remarkFlag;
+
+    /**
+     * 买家备注
+     * <p>买家下单时填写的订单备注</p>
+     */
+    private String buyerMessage;
+
+    /**
+     * 客服备注
+     * <p>商家客服对订单进行的备注内容</p>
+     */
+    private String sellerMemo;
+
+    /**
+     * 标旗
+     * <p>客服标旗,取值0至5对应的标旗颜色依次为灰(无标旗)、红、黄、绿、蓝、紫,不传默认0</p>
+     */
+    private Integer sellerFlag;
+
+    /**
+     * 邮费
+     * <p>商家收取买家的物流或者快递费用</p>
+     */
+    private BigDecimal postAmount;
+
+    /**
+     * 货到付款金额
+     * <p>货到付款金额   cod金额=(price * num + adjust_amount -discount – share_discount)+post_amount+ext_cod_fee-paid</p>
+     */
+    private BigDecimal codAmount;
+
+    /**
+     * 货到付款买家费用
+     * <p>货到付款买家费用,扣除货到付款订单金额后,卖家仍需支付的货到付款其他金额。这个钱卖家收不回来,是快递公司直接收走,但在快递单里是要打印出来,否则快递收款就错了</p>
+     */
+    private BigDecimal extCodFee;
+
+    /**
+     * 其它收费
+     * <p>其它应从买家收取的服务费,其他费用</p>
+     */
+    private BigDecimal otherAmount;
+
+    /**
+     * 已付
+     * <p>订单已付金额,paid计算公式:paid = Σ(price * num + adjust_amount -discount – share_discount)+ post_amount+other_amount,所有金额相关字段推送处理办法,单击这里</p>
+     */
+    private BigDecimal paid;
+
+    /**
+     * 证件类型
+     * <p>1:身份证(不传默认为0,为0时将证件号码置空)</p>
+     */
+    private Integer idCardType;
+
+    /**
+     * 证件号码
+     */
+    private String idCard;
+
+    /**
+     * 自动流转模式
+     * <p>是否为自动流转模式(1:是 0:不是 不传默认0),非自动流转模式一定不要传值。自动流转模式处理办法,”自动流转模式处理办法详解”)单击这里</p>
+     */
+    private Integer isAutoWms;
+
+    /**
+     * 仓库类型
+     * <p>非自动流转模式一定不要传值</p>
+     */
+    private Integer wmsType;
+
+    /**
+     * 仓库编号
+     * <p>ERP内自定义的仓库编号,查看路径ERP→设置→基本设置→仓库→仓库列表,测试环境仓库编号查看测试环境分配邮件。平台订单需指定ERP内仓库时,传哪个仓库的编号,ERP将为订单选择哪个仓库。</p>
+     */
+    private String warehouseNo;
+
+    /**
+     * 订单货品明细节点
+     * <p>货品明细列表(子订单列表)节点</p>
+     */
+    private List<ErpWdtOrder> orderList;
+}

+ 186 - 0
fs-service-system/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java

@@ -0,0 +1,186 @@
+package com.fs.erp.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.serializer.SerializeConfig;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.erp.domain.ErpOrder;
+import com.fs.erp.domain.ErpRefundOrder;
+import com.fs.erp.dto.*;
+import com.fs.erp.dto.sdk.wangdian.api.WdtClient;
+import com.fs.erp.dto.sdk.wangdian.enums.*;
+import com.fs.erp.dto.wdt.ErpWdtApiResponse;
+import com.fs.erp.dto.wdt.ErpWdtBusinessRequestParams;
+import com.fs.erp.dto.wdt.ErpWdtOrder;
+import com.fs.erp.dto.wdt.ErpWdtTrade;
+import com.fs.erp.service.IErpOrderService;
+import com.fs.store.domain.FsStoreOrder;
+import com.fs.store.domain.FsStoreProduct;
+import com.fs.store.service.IFsStoreOrderItemService;
+import com.fs.store.service.IFsStoreOrderService;
+import com.fs.store.service.IFsStoreProductService;
+import com.fs.store.vo.FsStoreOrderItemVO;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.http.util.Asserts;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.HashMap;
+
+@Slf4j
+@Service
+@Primary
+public class WdtErpOrderServiceImpl implements IErpOrderService {
+
+    @Autowired
+    private IFsStoreOrderService fsStoreOrderService;
+
+    @Autowired
+    private IFsStoreOrderItemService fsStoreOrderItemService;
+
+    @Autowired
+    private IFsStoreProductService fsStoreProductService;
+
+    @Value("${fsConfig.erpWdShopCode}")
+    private String shopCode;
+    @Override
+    public ErpOrderResponse addOrder(ErpOrder order) {
+        WdtClient client = WdtClient.getInstance();
+        //测试环境sid、appkey、密钥请到旺店通开放平台-自助对接-申请测试环境内查看,测试环境url=https://sandbox.wangdian.cn/openapi2/
+        //调用正式环境时请将sid、appkey、appsecret切换为实际参数,参数在旺店通开放平台-自助对接-应用管理内应用状态为已上线的应用中查看,调用正式环境注意切换正式环境url=https://api.wangdian.cn/openapi2/
+
+        ErpWdtBusinessRequestParams erpWdtBusinessRequestParams = new ErpWdtBusinessRequestParams();
+        erpWdtBusinessRequestParams.setShopNo(shopCode);
+        erpWdtBusinessRequestParams.setSwitchMode(0);
+        ErpWdtTrade erpWdtTrade = new ErpWdtTrade();
+
+        // 订单id
+        erpWdtTrade.setTid(order.getPlatform_code());
+        // 平台状态
+        erpWdtTrade.setTradeStatus(TradeStatus.SHIPPED.getValue());
+        // 支付状态
+        erpWdtTrade.setPayStatus(PaymentStatus.FULLY_PAID.getValue());
+        // 发货条件
+        erpWdtTrade.setDeliveryTerm(DeliveryTerm.PAYMENT_BEFORE_DELIVERY.getValue());
+        // 下单时间
+        erpWdtTrade.setTradeTime(order.getDeal_datetime());
+        // 支付时间
+        erpWdtTrade.setPayTime(order.getDeal_datetime());
+        // 分销类别
+        erpWdtTrade.setFenxiaoType(FenxiaoType.JINGXIAO.getValue());
+        // 客户网名
+        erpWdtTrade.setBuyerNick(order.getReceiver_name());
+        // 收件人姓名
+        erpWdtTrade.setReceiverName(order.getReceiver_name());
+        // 省份
+        erpWdtTrade.setReceiverProvince(order.getReceiver_province());
+        // 市
+        erpWdtTrade.setReceiverCity(order.getReceiver_city());
+        // 区
+        erpWdtTrade.setReceiverDistrict(order.getReceiver_district());
+        // 详细地址
+        erpWdtTrade.setReceiverAddress(order.getReceiver_address());
+        // 手机
+        erpWdtTrade.setReceiverMobile(order.getReceiver_mobile());
+        // 固定电话
+        erpWdtTrade.setReceiverTelno(order.getReceiver_mobile());
+        // 买家备注
+        erpWdtTrade.setBuyerMessage(order.getBuyer_memo());
+        // 卖家备注
+        erpWdtTrade.setSellerMemo(order.getSeller_memo());
+//        erpWdtTrade.setWarehouseNo(order.getWarehouse_code());
+        erpWdtTrade.setWarehouseNo("beiliyou2-test");
+        // 已付金额
+        FsStoreOrder fsStoreOrder = fsStoreOrderService.selectFsStoreOrderByOrderCode(order.getPlatform_code());
+        if(ObjectUtils.isNotNull(fsStoreOrder)){
+            erpWdtTrade.setPaid(fsStoreOrder.getPayMoney());
+        }
+
+        List<FsStoreOrderItemVO> fsStoreOrderItemVOS = fsStoreOrderItemService.selectFsStoreOrderItemListByOrderId(fsStoreOrder.getId());
+        List<ErpWdtOrder> erpWdtOrderList = new ArrayList<>();
+
+        for (FsStoreOrderItemVO fsStoreOrderItem : fsStoreOrderItemVOS) {
+            ErpWdtOrder erpWdtOrder = new ErpWdtOrder();
+            //平台订单货品表主键
+            erpWdtOrder.setOid(String.valueOf(fsStoreOrderItem.getItemId()));
+            erpWdtOrder.setNum(BigDecimal.valueOf(fsStoreOrderItem.getNum()));
+            FsStoreProduct fsStoreProduct = fsStoreProductService.selectFsStoreProductById(fsStoreOrderItem.getProductId());
+            Asserts.check(ObjectUtils.isNotNull(fsStoreProduct),"该产品不存在! 产品id: {} ",fsStoreOrderItem.getProductId());
+            // 单价
+            erpWdtOrder.setPrice(fsStoreProduct.getPrice());
+            // 状态
+            erpWdtOrder.setStatus(TradeStatus.SHIPPED.getValue());
+            // 退款状态
+            erpWdtOrder.setRefundStatus(RefundStatus.NO_REFUND.getValue());
+            // 平台货品ID
+            erpWdtOrder.setGoodsId(String.valueOf(fsStoreProduct.getProductId()));
+            JSONObject jsonObject = JSONObject.parseObject(fsStoreOrderItem.getJsonInfo());
+            erpWdtOrder.setSpecId(jsonObject.getString("sku"));
+            erpWdtOrder.setGoodsNo(jsonObject.getString("sku"));
+            // 货品名称
+            erpWdtOrder.setGoodsName(fsStoreProduct.getProductName());
+            // 调整
+            erpWdtOrder.setAdjustAmount(BigDecimal.ZERO);
+            // 优惠
+            erpWdtOrder.setDiscount(BigDecimal.ZERO);
+            // 分摊优惠
+            erpWdtOrder.setShareDiscount(BigDecimal.ZERO);
+            erpWdtOrderList.add(erpWdtOrder);
+        }
+
+        erpWdtTrade.setOrderList(erpWdtOrderList);
+        erpWdtBusinessRequestParams.setTradeList(Arrays.asList(erpWdtTrade));
+
+        Map<String,String> map = new HashMap<>();
+        map.put("shop_no",erpWdtBusinessRequestParams.getShopNo());
+        map.put("switch_mode", String.valueOf(erpWdtBusinessRequestParams.getSwitchMode()));
+        map.put("trade_list", convertToSnakeCase(erpWdtBusinessRequestParams.getTradeList()));
+
+
+        try {
+            String response = client.execute("trade_push.php", map);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
+            if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
+                log.info("订单推送成功: {}", response);
+                return new ErpOrderResponse();
+            } else {
+                throw new RuntimeException(String.format("订单推送失败,原因: %s",erpWdtApiResponse.getMessage()));
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return new ErpOrderResponse();
+    }
+    public static String convertToSnakeCase(Object obj) {
+        SerializeConfig config = new SerializeConfig();
+        config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
+        return JSON.toJSONString(obj, config);
+    }
+    @Override
+    public ErpOrderResponse refundOrder(ErpRefundOrder order) {
+        return null;
+    }
+
+    @Override
+    public ErpDeliverysResponse getDeliver(ErpDeliverysRequest param) {
+        return null;
+    }
+
+    @Override
+    public ErpOrderQueryResponse getOrder(ErpOrderQueryRequert param) {
+        return null;
+    }
+
+    @Override
+    public BaseResponse refundUpdate(ErpRefundUpdateRequest param) {
+        return null;
+    }
+}

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

@@ -1390,7 +1390,7 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
     @Override
     public void createOmsOrder(Long orderId) throws ParseException {
         FsStoreOrder order=fsStoreOrderMapper.selectFsStoreOrderById(orderId);
-        if(StringUtils.isEmpty(order.getExtendOrderId())&&order.getStatus()!=1){
+        if(!StringUtils.isEmpty(order.getExtendOrderId())&&order.getStatus()!=1){
             return;
         }
         ErpOrder erpOrder=new ErpOrder();

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

@@ -13,6 +13,12 @@ fsConfig:
   erpSecret: 96f774dbd60847b59a16f92fd963a0c8
   erpUrl: http://v2.api.guanyierp.com/rest/erp_open
   erpShopCode: test
+  #ERP-hc
+  erpWdAppKey: beiliyou2-test
+  erpWdAppsecret: 6ec212f06
+  erpWdSid: apidevnew2
+  erpWdShopCode: beiliyou2-test
+  erpWdBaseUrl: https://sandbox.wangdian.cn/openapi2/
   #第三方支付配置
   payOpen: 1
   payPartnerId: 22051909542647100020