From 028376d2210a373599b29dd724beff1017048ae5 Mon Sep 17 00:00:00 2001 From: 18650502300 <18650502300@163.com> Date: Tue, 24 Sep 2024 11:10:29 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=A4=AA=E7=B1=B3=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-druid.yml | 2 +- .../com/ruoyi/common/enums/PayChannel.java | 3 +- .../com/ruoyi/common/pay/tm/Application.java | 87 +++++++++++++++++++ .../com/ruoyi/common/pay/tm/IChannelInfo.java | 18 ++++ .../com/ruoyi/common/pay/tm/Md5Utils.java | 59 +++++++++++++ .../com/ruoyi/common/pay/tm/StringUtils.java | 73 ++++++++++++++++ .../com/ruoyi/common/pay/tm/TmPayService.java | 84 ++++++++++++++++++ .../ruoyi/common/utils/http/HttpUtils.java | 60 +++++++++++++ .../java/com/ruoyi/system/domain/Channel.java | 15 ++++ .../com/ruoyi/system/domain/ChannelVO.java | 4 +- .../system/service/impl/WxPayService.java | 14 +-- 11 files changed, 409 insertions(+), 10 deletions(-) create mode 100644 electripper-common/src/main/java/com/ruoyi/common/pay/tm/Application.java create mode 100644 electripper-common/src/main/java/com/ruoyi/common/pay/tm/IChannelInfo.java create mode 100644 electripper-common/src/main/java/com/ruoyi/common/pay/tm/Md5Utils.java create mode 100644 electripper-common/src/main/java/com/ruoyi/common/pay/tm/StringUtils.java create mode 100644 electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayService.java diff --git a/electripper-admin/src/main/resources/application-druid.yml b/electripper-admin/src/main/resources/application-druid.yml index e174da8..355b4b5 100644 --- a/electripper-admin/src/main/resources/application-druid.yml +++ b/electripper-admin/src/main/resources/application-druid.yml @@ -6,7 +6,7 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://localhost:3306/electripper?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://localhost:3306/ele2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 # url: jdbc:mysql://117.26.179.22:61110/electripper?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 diff --git a/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java b/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java index a6bdc28..a16b4ef 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java +++ b/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java @@ -13,7 +13,8 @@ import lombok.Getter; @AllArgsConstructor public enum PayChannel { CT_WX("ctwx", "创特微信支付"), - TL_WX("tlwx", "通联微信支付"), +// TL_WX("tlwx", "通联微信支付"), + TM_WX("tmwx", "太米微信支付"), YS_WX("yswx", "嵛山岛微信支付"); private final String code; diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Application.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Application.java new file mode 100644 index 0000000..b1e5045 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Application.java @@ -0,0 +1,87 @@ +package com.ruoyi.common.pay.tm; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.utils.http.HttpUtils; + +import java.util.HashMap; + +public class Application { + + private final static String HTTP = "https://pos.weixincore.com"; + private final static String SIGNKEY = "ac6d97e67b444b7a43edfc9182634786"; + + public static void main(String[] args) { + pay(); + } + + /** + * 订单查询 + */ + public static void orderQuery() { + HashMap body = new HashMap(); + body.put("tradeId", "1"); + body.put("terminalType", "1"); + body.put("shopId", "488"); + doPost("/open/Pay/orderQuery", body); + } + + /** + * 退款 + */ + public static void refund() { + HashMap body = new HashMap(); + body.put("refundFee", "0.01"); + body.put("terminalType", "1"); + body.put("tradeId", "1"); + body.put("shopId", "488"); + doPost("/open/Pay/refund", body); + } + + /** + * 付款码支付V2 + */ + public static void microPayV2() { + HashMap body = new HashMap(); + body.put("payAmount", "0.01"); + body.put("terminalType", "1"); + body.put("authCode", "3865199665693980"); + body.put("shopId", "488"); + doPost("/open/Pay/microPayV2", body); + } + + /** + * jsapi支付 + */ + public static void pay() { + HashMap body = new HashMap(); + body.put("payAmount", "1"); + body.put("terminalType", "1"); + body.put("shopId", "488"); + // 填充必填字段 + body.put("sn", "deviceSN123456"); // 设备编号,需替换为真实设备号 + body.put("payType", "wx_pay"); // 支付方式,可以是 wx.pay, ali.pay, union.online + body.put("outTradeId", "tradeId123"); // 商户订单号 + body.put("body", "商品描述"); // 商品描述 + body.put("notifyUrl", "https://yourdomain.com/notify"); // 异步回调URL + body.put("frontUrl", "https://yourdomain.com/front"); // 前端页面跳转URL + body.put("profitSharing", "N"); // 是否分账,示例填写 N + doPost("/open/Pay/unifiedOrder", body); + } + + private static void doPost(String url, HashMap body) { + body.put("developerId", "100001"); + body.put("version", "1.0"); + body.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + body.put("nonceStr", StringUtils.getRandomString(16)); + String bodyStr = StringUtils.getAsciiSort(body); + String sign = Md5Utils.getMD5Code(bodyStr+"&key="+SIGNKEY).toUpperCase(); + body.put("sign", sign); + + HashMap headerData = new HashMap(); + headerData.put("Content-Type", "application/json"); + + String response = HttpUtils.sendPostWithHeaders(HTTP + url, headerData,JSON.toJSONString(body)); + System.out.println("API Response: " + response); + } + +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/IChannelInfo.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/IChannelInfo.java new file mode 100644 index 0000000..941b73d --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/IChannelInfo.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.pay.tm; + +public interface IChannelInfo { + + String getDeveloperId(); + + String getShopId(); + + String getHttpUrl(); + + String getSignKey(); + + String getNotifyUrl(); + + String getFrontUrl(); + + String getSn(); +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Md5Utils.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Md5Utils.java new file mode 100644 index 0000000..87b5ab2 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Md5Utils.java @@ -0,0 +1,59 @@ +package com.ruoyi.common.pay.tm; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Md5Utils { + + // 全局数组 + private final static String[] strDigits = { "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; + + // 返回形式为数字跟字符串 + public static String byteToArrayString(byte bByte) { + int iRet = bByte; + if (iRet < 0) { + iRet += 256; + } + int iD1 = iRet / 16; + int iD2 = iRet % 16; + return strDigits[iD1] + strDigits[iD2]; + } + + // 返回形式只为数字 + public static String byteToNum(byte bByte) { + int iRet = bByte; + if (iRet < 0) { + iRet += 256; + } + return String.valueOf(iRet); + } + + // 转换字节数组为16进制字串 + public static String byteToString(byte[] bByte) { + StringBuffer sBuffer = new StringBuffer(); + for (int i = 0; i < bByte.length; i++) { + sBuffer.append(byteToArrayString(bByte[i])); + } + return sBuffer.toString(); + } + + /** + * md5 加密 + * @param strObj + * @return + */ + public static String getMD5Code(String strObj) { + String resultString = null; + try { + resultString = new String(strObj); + MessageDigest md = MessageDigest.getInstance("MD5"); + // md.digest() 该函数返回值为存放哈希值结果的byte数组 + resultString = byteToString(md.digest(strObj.getBytes())); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + } + return resultString; + } + +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/StringUtils.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/StringUtils.java new file mode 100644 index 0000000..c586be4 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/StringUtils.java @@ -0,0 +1,73 @@ +package com.ruoyi.common.pay.tm; + +import java.util.*; +import java.util.Map.Entry; + +public class StringUtils { + + private static Random random = null; + + /** + * 获取随机数 + * @param length + * @return + */ + public static String getRandomString(int length) { + // 定义一个字符串(A-Z,a-z1-9)即62位; + String str = "zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; + // 由Random生成随机数 + if (random == null) { + random = new Random(); + } + StringBuffer sb = new StringBuffer(); + // 长度为几就循环几次 + for (int i = 0; i < length; ++i) { + // 产生0-61的数字 + int number = random.nextInt(62); + // 将产生的数字通过length次承载到sb中 + sb.append(str.charAt(number)); + } + // 将承载的字符转换成字符串 + return sb.toString(); + } + + public static String getUUID() { + return UUID.randomUUID().toString(); + } + + public static String getUUIDNoLine() { + String s = UUID.randomUUID().toString(); + return s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24); + } + + /** + * 参数名ASCII码从小到大排序(字典序) + * @param map + * @return + */ + public static String getAsciiSort(Map map) { + List> infoIds = new ArrayList>(map.entrySet()); + // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) + Collections.sort(infoIds, new Comparator>() { + public int compare(Entry o1, Entry o2) { + return ((String) o1.getKey()).compareToIgnoreCase((String) o2.getKey()); + } + }); + // 构造签名键值对的格式 + StringBuilder sb = new StringBuilder(); + for (Entry item : infoIds) { + if (item.getKey() != null || item.getKey() != "") { + String key = item.getKey(); + String val = item.getValue(); + if (!(val == "" || val == null)) { + sb.append(key + "=" + val + "&"); + } + } + } + if(sb.toString().endsWith("&")) { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayService.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayService.java new file mode 100644 index 0000000..d8d02a3 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayService.java @@ -0,0 +1,84 @@ +package com.ruoyi.common.pay.tm; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.pay.wx.Payable; +import com.ruoyi.common.pay.wx.RefundAble; +import com.ruoyi.common.utils.http.HttpUtils; +import org.springframework.stereotype.Service; +import java.util.HashMap; + +/** + * 太米支付 + */ +@Service +public class TmPayService { + + /** + * 订单查询 + */ + public static void orderQuery(IChannelInfo channel, String outTradeNo) { + HashMap body = new HashMap(); + body.put("tradeId", outTradeNo); + body.put("terminalType", "1"); + body.put("shopId", channel.getShopId()); + doPost("/open/Pay/orderQuery", body,channel); + } + + /** + * 退款 + */ + public static void refund(IChannelInfo channel,RefundAble refundAble) { + HashMap body = new HashMap(); + body.put("refundFee", String.valueOf(refundAble.getAmount())); + body.put("terminalType", "1"); + body.put("tradeId", refundAble.getOutRefundNo()); + body.put("shopId", channel.getShopId()); + doPost("/open/Pay/refund", body,channel); + } + + /** + * 关闭订单 + */ + public static void closeOrder(IChannelInfo channel, String outTradeNo) { + HashMap body = new HashMap(); + body.put("tradeId", outTradeNo); + body.put("terminalType", "1"); + body.put("shopId", channel.getShopId()); + doPost("/open/Pay/orderQuery", body,channel); + } + + /** + * JSAPI支付 + */ + public void pay(IChannelInfo channel, Payable payable) { + HashMap body = new HashMap<>(); + body.put("payAmount", String.valueOf(payable.getAmount())); + body.put("terminalType", "1"); + body.put("shopId", channel.getShopId()); // 从渠道获取shopId + body.put("sn", channel.getSn()); + body.put("payType", "wx_pay"); + body.put("outTradeId", payable.getOutTradeNo()); + body.put("body", payable.getDescription()); + body.put("notifyUrl", channel.getNotifyUrl()); + body.put("frontUrl", channel.getFrontUrl()); + + doPost(channel.getHttpUrl() + "/open/Pay/unifiedOrder", body, channel); + } + + private static void doPost(String url, HashMap body, IChannelInfo channel) { + body.put("developerId", channel.getDeveloperId()); + body.put("version", "1.0"); + body.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + body.put("nonceStr", StringUtils.getRandomString(16)); + + String bodyStr = StringUtils.getAsciiSort(body); + String sign = Md5Utils.getMD5Code(bodyStr + "&key=" + channel.getSignKey()).toUpperCase(); + body.put("sign", sign); + + HashMap headerData = new HashMap<>(); + headerData.put("Content-Type", "application/json"); + + String response = HttpUtils.sendPostWithHeaders(url, headerData, JSON.toJSONString(body)); + System.out.println("API Response: " + response); + } +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java index 9f75549..37608ee 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java +++ b/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -11,6 +11,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; +import java.util.Map; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -211,6 +212,65 @@ public class HttpUtils } } + /** + * 向指定 URL 发送 POST 方法的请求,并支持自定义请求头和 JSON 请求体 + * + * @param url 发送请求的 URL + * @param headerData 请求头信息,键值对形式 + * @param body 请求体,通常为 JSON 格式的字符串 + * @return 所代表远程资源的响应结果 + */ + public static String sendPostWithHeaders(String url, Map headerData, String body) { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + log.info("sendPostWithHeaders - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + + // 设置请求头信息 + for (Map.Entry entry : headerData.entrySet()) { + conn.setRequestProperty(entry.getKey(), entry.getValue()); + } + conn.setDoOutput(true); + conn.setDoInput(true); + // 发送 POST 请求体数据 + out = new PrintWriter(conn.getOutputStream()); + out.print(body); // 发送 JSON 格式的 body + out.flush(); + + // 读取响应数据 + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendPostWithHeaders ConnectException, url=" + url + ", body=" + body, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendPostWithHeaders SocketTimeoutException, url=" + url + ", body=" + body, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendPostWithHeaders IOException, url=" + url + ", body=" + body, e); + } catch (Exception e) { + log.error("调用HttpUtils.sendPostWithHeaders Exception, url=" + url + ", body=" + body, e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + log.error("调用in.close Exception, url=" + url + ", body=" + body, ex); + } + } + return result.toString(); + } + + /** * 向指定 URL 发送POST方法的请求 * diff --git a/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java b/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java index 2ad85f6..33df79c 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java +++ b/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java @@ -64,4 +64,19 @@ public class Channel extends BaseEntity /** appid */ private String appid; + /** 支付完成后的跳转地址 */ + private String frontUrl; + + private String developerId; + + /** 门店Id */ + private String shopId; + + private String httpUrl; + + private String signKey; + + /** 终端sn */ + private String sn; + } diff --git a/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java b/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java index 4917c5f..538834c 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java +++ b/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java @@ -1,5 +1,6 @@ package com.ruoyi.system.domain; +import com.ruoyi.common.pay.tm.IChannelInfo; import lombok.Data; /** @@ -7,5 +8,6 @@ import lombok.Data; * 2024/7/28 */ @Data -public class ChannelVO extends Channel{ +public class ChannelVO extends Channel implements IChannelInfo { + } diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java index 35cde1d..a64460e 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java @@ -122,7 +122,7 @@ public class WxPayService implements IWxPayService { String outTradeNo = null; if(PayChannel.CT_WX.equalsCode(channelVO.getCode()) || PayChannel.YS_WX.equalsCode(channelVO.getCode())){ outTradeNo = IdUtils.getOrderNo("wx"); - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ outTradeNo = IdUtils.getOrderNo("tlwx"); } String type = order.getType(); @@ -162,8 +162,8 @@ public class WxPayService implements IWxPayService { jsapiServiceExtension.closeOrder(closeOrderRequest); } return res; - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ - log.info("----------{}-------------","通联微信支付"); + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ + log.info("----------{}-------------","太米微信支付"); if(StrUtil.isNotBlank(order.getOutTradeNo())){ // 关闭订单 @@ -247,7 +247,7 @@ public class WxPayService implements IWxPayService { } return res; - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ log.info("----优惠券------{}-------------","通联微信支付"); // 获取JSAPI所需参数 @@ -313,7 +313,7 @@ public class WxPayService implements IWxPayService { log.info("微信查询订单信息outTradeNo={}-----【{}】",outTradeNo,JSON.toJSON(transaction)); paymentResult1.setTransaction(transaction); return paymentResult1; - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ Map result = sybPayService.queryOrderByOutTradeNo(order.getOutTradeNo()); if(SybTrxStatus.isSuccess(result.get("trxstatus"))) { paymentResult1.setResult(result); @@ -388,7 +388,7 @@ public class WxPayService implements IWxPayService { } return true; } - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ Map result = sybPayService.queryOrderByOutTradeNo(order.getOutTradeNo()); if(SybTrxStatus.isSuccess(result.get("trxstatus"))) { return true; @@ -439,7 +439,7 @@ public class WxPayService implements IWxPayService { RefundService refundService = getRefundService(channelVO); Refund refund = refundService.create(request); log.info("【退款】微信返回结果:【{}】",JSON.toJSONString(refund)); - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ log.info("----------{}-------------","通联微信退款"); RefundAble refundAble = new RefundAble(); refundAble.setOutTradeNo(etOrder.getOutTradeNo());