package com.ruoyi.other.util.payment;
|
|
import cn.hutool.http.Header;
|
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpResponse;
|
import cn.hutool.http.HttpUtil;
|
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSONObject;
|
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.other.util.UUIDUtil;
|
import com.ruoyi.other.util.payment.model.*;
|
import lombok.extern.slf4j.Slf4j;
|
|
import java.math.BigDecimal;
|
import java.net.InetAddress;
|
import java.text.SimpleDateFormat;
|
import java.util.*;
|
|
/**
|
* 支付工具类
|
* @author zhibing.pu
|
* @Date 2024/12/27 17:00
|
*/
|
@Slf4j
|
public class PaymentUtil {
|
|
//微信公众号、微信小程序、微信 APP+/H5、云微小程序支付
|
private static final String appId = "wx049faf9c5234f31c";
|
/**
|
* 商户密钥
|
*/
|
private static final String key = "ss369875124965782f148539657826321";
|
/**
|
* 商户号
|
*/
|
private static final String merchantNo = "1717539630";
|
/**
|
* 平台-报备商户号
|
*/
|
private static final String sysTradeMerchantNo = "";
|
/**
|
* 支付回调地址
|
*/
|
private static final String callbackUrl = "https://221.182.45.100:8084";
|
|
|
/**
|
* 支付
|
* @param orderNo 商户订单号
|
* @param amount 订单金额
|
* @param productName 商品名称
|
* @param productDesc 商品描述
|
* @param mp 公用回传参数
|
* @param notifyUrl 服务器异步通知地址
|
* @param openId 微信 Openid
|
* @param tradeMerchantNo 报备商户号
|
* @return
|
*/
|
public static UniPayResult uniPay(String orderNo, Double amount, String productName, String productDesc, String mp, String notifyUrl, String openId, String tradeMerchantNo){
|
String url = "https://trade.joinpay.com/tradeRt/uniPay";
|
HttpRequest post = HttpUtil.createPost(url);
|
post.header(Header.CONTENT_TYPE, "application/x-www-form-urlencoded");
|
JSONObject body = new JSONObject();
|
//版本号
|
body.put("p0_Version", "2.5");
|
//商户编号
|
body.put("p1_MerchantNo", merchantNo);
|
//商户订单号
|
body.put("p2_OrderNo", orderNo);
|
//订单金额
|
body.put("p3_Amount", amount);
|
//交易币种
|
body.put("p4_Cur", "1");
|
//商品名称
|
body.put("p5_ProductName", productName);
|
//商品描述
|
body.put("p6_ProductDesc", productDesc);
|
//公用回传参数
|
body.put("p7_Mp", mp);
|
//服务器异步通知地址
|
body.put("p9_NotifyUrl", callbackUrl + notifyUrl);
|
//交易类型
|
body.put("q1_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
|
//微信 Openid
|
body.put("q5_OpenId", openId);
|
//APPID
|
body.put("q7_AppId", appId);
|
//报备商户号
|
body.put("qa_TradeMerchantNo", StringUtils.isNotEmpty(tradeMerchantNo) ? tradeMerchantNo : sysTradeMerchantNo);
|
String sign = null;
|
try {
|
sign = sign(body);
|
} catch (Exception e) {
|
throw new RuntimeException(e);
|
}
|
body.put("hmac", sign);
|
post.form(body);
|
log.info("支付接口请求参数:" + body);
|
HttpResponse execute = post.execute();
|
log.info("支付接口请求响应:" + execute.body());
|
if(200 != execute.getStatus()){
|
log.error("支付接口异常:" + execute.body());
|
return null;
|
}
|
UniPayResult uniPayResult = JSON.parseObject(execute.body(), UniPayResult.class);
|
return uniPayResult;
|
}
|
/**
|
* native支付
|
* @param orderNo 商户订单号
|
* @param amount 订单金额
|
* @param notifyUrl 服务器异步通知地址
|
* @return
|
*/
|
public static String nativePay(String orderNo, BigDecimal amount, String notifyUrl) throws Exception {
|
int totalFee = amount.multiply(new BigDecimal("100")).intValue();
|
|
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
|
|
// 构建 XML 请求体
|
Map<String, Object> params = new HashMap<>();
|
params.put("appid", appId);
|
params.put("mch_id", merchantNo);
|
params.put("nonce_str", UUIDUtil.getRandomCode(16));
|
params.put("body", "积分充值");
|
params.put("out_trade_no", orderNo);
|
params.put("total_fee", totalFee);
|
// params.put("spbill_create_ip", InetAddress.getLocalHost().getHostAddress());
|
params.put("spbill_create_ip", "221.182.45.100");
|
params.put("notify_url", "https://221.182.45.100:8084/undif");
|
params.put("trade_type", "NATIVE");
|
params.put("product_id", "1");
|
|
String sign = sign1(JSONObject.from(params)); // 使用原来的 sign 方法
|
params.put("sign", sign);
|
|
String xmlBody = mapToXml(params); // 转换为 XML 字符串
|
|
// 发送请求
|
HttpRequest post = HttpUtil.createPost(url);
|
post.header(Header.CONTENT_TYPE, "application/xml");
|
post.body(xmlBody); // 发送原始 XML 字符串
|
|
log.info("Native支付接口请求参数:" + xmlBody);
|
HttpResponse execute = post.execute();
|
log.info("Native支付接口请求响应:" + execute.body());
|
|
if (execute.getStatus() != 200) {
|
log.error("Native支付接口异常:" + execute.body());
|
return null;
|
}
|
return execute.body();
|
}
|
private static String mapToXml(Map<String, Object> map) {
|
StringBuilder sb = new StringBuilder();
|
sb.append("<xml>");
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
String key = entry.getKey();
|
Object value = entry.getValue();
|
if (value != null && !value.toString().isEmpty()) {
|
sb.append("<").append(key).append(">");
|
sb.append(value);
|
sb.append("</").append(key).append(">");
|
}
|
}
|
sb.append("</xml>");
|
return sb.toString();
|
}
|
public static String sign1(JSONObject body) {
|
Set<Map.Entry<String, Object>> entries = body.entrySet();
|
List<Map.Entry<String, Object>> infoIds = new ArrayList<>(entries);
|
|
// 排除 sign 字段本身
|
infoIds.removeIf(entry -> "sign".equals(entry.getKey()));
|
|
// 按 ASCII 顺序排序
|
infoIds.sort(Map.Entry.comparingByKey());
|
|
StringBuilder sb = new StringBuilder();
|
for (Map.Entry<String, Object> entry : infoIds) {
|
Object val = entry.getValue();
|
if (val != null && !val.toString().trim().isEmpty()) {
|
sb.append(entry.getKey()).append("=").append(val).append("&");
|
}
|
}
|
|
// 最后拼接 &key=商户密钥
|
sb.append("key=").append(key);
|
|
String stringSignTemp = sb.toString();
|
log.info("待签名串:{}", stringSignTemp);
|
|
// 使用 MD5 加密
|
return MD5AndKL.MD5(stringSignTemp);
|
}
|
|
/**
|
* 微信下单的签名算法
|
*
|
* @param map
|
* @return
|
*/
|
private String weixinSignature(Map<String, Object> map) {
|
try {
|
Set<Map.Entry<String, Object>> entries = map.entrySet();
|
List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
|
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
|
Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
|
public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
|
return (o1.getKey()).toString().compareTo(o2.getKey());
|
}
|
});
|
// 构造签名键值对的格式
|
StringBuilder sb = new StringBuilder();
|
for (Map.Entry<String, Object> item : infoIds) {
|
if (item.getKey() != null || item.getKey() != "") {
|
String key = item.getKey();
|
Object val = item.getValue();
|
if (!(val == "" || val == null)) {
|
sb.append(key + "=" + val + "&");
|
}
|
}
|
}
|
sb.append("key=" + key);
|
String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式
|
return sign;
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
return null;
|
}
|
public static void main(String[] args) throws Exception {
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
|
String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
|
String string = PaymentUtil.nativePay(code,new BigDecimal("0.1"),
|
"/test"
|
);
|
System.err.println(string);
|
}
|
/**
|
* 查询支付订单
|
* @param orderNo 订单号
|
* @return
|
*/
|
public static QueryOrderResult queryOrder(String orderNo){
|
String url = "https://trade.joinpay.com/tradeRt/queryOrder";
|
HttpRequest post = HttpUtil.createPost(url);
|
post.header(Header.CONTENT_TYPE, "application/x-www-form-urlencoded");
|
JSONObject body = new JSONObject();
|
//版本号
|
body.put("p0_Version", "2.5");
|
//商户编号
|
body.put("p1_MerchantNo", merchantNo);
|
//商户订单号
|
body.put("p2_OrderNo", orderNo);
|
String sign = null;
|
try {
|
sign = sign(body);
|
} catch (Exception e) {
|
throw new RuntimeException(e);
|
}
|
body.put("hmac", sign);
|
post.form(body);
|
log.info("查询支付接口请求参数:" + body);
|
HttpResponse execute = post.execute();
|
log.info("查询支付接口请求响应:" + execute.body());
|
if(200 != execute.getStatus()){
|
log.error("查询支付接口异常:" + execute.body());
|
return null;
|
}
|
QueryOrderResult uniPayResult = JSON.parseObject(execute.body(), QueryOrderResult.class);
|
return uniPayResult;
|
}
|
|
|
/**
|
* 退款
|
* @param orderNo 支付订单号
|
* @param refundOrderNo 退款订单号
|
* @param refundAmount 退款金额
|
* @param notifyUrl 异步通知地址
|
* @return
|
*/
|
public static RefundResult refund(String orderNo, String refundOrderNo, Double refundAmount, String notifyUrl){
|
String url = "https://trade.joinpay.com/tradeRt/refund";
|
HttpRequest post = HttpUtil.createPost(url);
|
post.header(Header.CONTENT_TYPE, "application/x-www-form-urlencoded");
|
JSONObject body = new JSONObject();
|
//版本号
|
body.put("p0_Version", "2.3");
|
//商户编号
|
body.put("p1_MerchantNo", merchantNo);
|
//商户订单号
|
body.put("p2_OrderNo", orderNo);
|
//商户退款订单号
|
body.put("p3_RefundOrderNo", refundOrderNo);
|
//退款金额
|
body.put("p4_RefundAmount", refundAmount);
|
//服务器异步通知地址
|
body.put("p6_NotifyUrl", notifyUrl);
|
String sign = null;
|
try {
|
sign = sign(body);
|
} catch (Exception e) {
|
throw new RuntimeException(e);
|
}
|
body.put("hmac", sign);
|
post.form(body);
|
log.info("退款接口请求参数:" + body);
|
HttpResponse execute = post.execute();
|
log.info("退款接口请求响应:" + execute.body());
|
if(200 != execute.getStatus()){
|
log.error("退款接口异常:" + execute.body());
|
return null;
|
}
|
RefundResult uniPayResult = JSON.parseObject(execute.body(), RefundResult.class);
|
return uniPayResult;
|
}
|
|
|
/**
|
* 查询退款订单
|
* @param refundOrderNo 退款订单号
|
* @return
|
*/
|
public static QueryRefundResult queryRefund(String refundOrderNo){
|
String url = "https://trade.joinpay.com/tradeRt/refund";
|
HttpRequest post = HttpUtil.createPost(url);
|
post.header(Header.CONTENT_TYPE, "application/x-www-form-urlencoded");
|
JSONObject body = new JSONObject();
|
//版本号
|
body.put("p0_Version", "2.3");
|
//商户编号
|
body.put("p1_MerchantNo", merchantNo);
|
//商户退款订单号
|
body.put("p2_RefundOrderNo", refundOrderNo);
|
String sign = null;
|
try {
|
sign = sign(body);
|
} catch (Exception e) {
|
throw new RuntimeException(e);
|
}
|
body.put("hmac", sign);
|
post.form(body);
|
log.info("退款接口请求参数:" + body);
|
HttpResponse execute = post.execute();
|
log.info("退款接口请求响应:" + execute.body());
|
if(200 != execute.getStatus()){
|
log.error("退款接口异常:" + execute.body());
|
return null;
|
}
|
QueryRefundResult uniPayResult = JSON.parseObject(execute.body(), QueryRefundResult.class);
|
return uniPayResult;
|
}
|
|
|
/**
|
* 关闭订单(仅支持微信和支付宝)
|
* @param orderNo 订单号
|
* @return
|
*/
|
public static CloseOrderResult closeOrder(String orderNo){
|
String url = "https://www.joinpay.com/trade/closeOrder.action";
|
HttpRequest post = HttpUtil.createPost(url);
|
post.header(Header.CONTENT_TYPE, "application/x-www-form-urlencoded");
|
JSONObject body = new JSONObject();
|
//商户编号
|
body.put("p1_MerchantNo", merchantNo);
|
//商户订单号
|
body.put("p2_OrderNo", orderNo);
|
//交易类型
|
body.put("p3_FrpCode", FrpCodeEnum.WEIXIN_XCX.getCode());
|
String sign = null;
|
try {
|
sign = sign(body);
|
} catch (Exception e) {
|
throw new RuntimeException(e);
|
}
|
body.put("hmac", sign);
|
post.form(body);
|
log.info("关闭订单接口请求参数:" + body);
|
HttpResponse execute = post.execute();
|
log.info("关闭订单接口请求响应:" + execute.body());
|
if(200 != execute.getStatus()){
|
log.error("关闭订单接口异常:" + execute.body());
|
return null;
|
}
|
CloseOrderResult uniPayResult = JSON.parseObject(execute.body(), CloseOrderResult.class);
|
return uniPayResult;
|
}
|
|
|
|
public static String sign(JSONObject body) {
|
Set<Map.Entry<String, Object>> entries = body.entrySet();
|
List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries);
|
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
|
Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
|
public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
|
return (o1.getKey()).compareTo(o2.getKey());
|
}
|
});
|
// 构造签名键值对的格式
|
StringBuilder sb = new StringBuilder();
|
for (Map.Entry<String, Object> item : infoIds) {
|
if (item.getKey() != null || item.getKey() != "") {
|
Object val = item.getValue();
|
if (!(val == "" || val == null)) {
|
sb.append(val);
|
}
|
}
|
}
|
sb.append(key);
|
log.info("待签名串:{}", sb.toString());
|
return MD5AndKL.MD5(sb.toString());
|
}
|
|
|
|
}
|