1.太米支付

This commit is contained in:
邱贞招 2024-09-24 11:10:29 +08:00
parent 596359030c
commit 028376d221
11 changed files with 409 additions and 10 deletions

View File

@ -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

View File

@ -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;

View File

@ -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<String, String> body = new HashMap<String, String>();
body.put("tradeId", "1");
body.put("terminalType", "1");
body.put("shopId", "488");
doPost("/open/Pay/orderQuery", body);
}
/**
* 退款
*/
public static void refund() {
HashMap<String, String> body = new HashMap<String, String>();
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<String, String> body = new HashMap<String, String>();
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<String, String> body = new HashMap<String, String>();
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<String, String> 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<String, String> headerData = new HashMap<String, String>();
headerData.put("Content-Type", "application/json");
String response = HttpUtils.sendPostWithHeaders(HTTP + url, headerData,JSON.toJSONString(body));
System.out.println("API Response: " + response);
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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-Za-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<String, String> map) {
List<Entry<String, String>> infoIds = new ArrayList<Entry<String, String>>(map.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序字典序
Collections.sort(infoIds, new Comparator<Entry<String, String>>() {
public int compare(Entry<String, String> o1, Entry<String, String> o2) {
return ((String) o1.getKey()).compareToIgnoreCase((String) o2.getKey());
}
});
// 构造签名键值对的格式
StringBuilder sb = new StringBuilder();
for (Entry<String, String> 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();
}
}

View File

@ -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<String, String> body = new HashMap<String, String>();
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<String, String> body = new HashMap<String, String>();
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<String, String> body = new HashMap<String, String>();
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<String, String> 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<String, String> 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<String, String> headerData = new HashMap<>();
headerData.put("Content-Type", "application/json");
String response = HttpUtils.sendPostWithHeaders(url, headerData, JSON.toJSONString(body));
System.out.println("API Response: " + response);
}
}

View File

@ -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<String, String> 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<String, String> 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方法的请求
*

View File

@ -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;
}

View File

@ -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 {
}

View File

@ -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<String, String> 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<String, String> 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());