|
|
|
@ -1,233 +0,0 @@
|
|
|
|
|
package com.sky.utils;
|
|
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
import com.sky.properties.WeChatProperties;
|
|
|
|
|
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
|
|
|
|
|
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
|
|
|
|
|
import org.apache.commons.lang.RandomStringUtils;
|
|
|
|
|
import org.apache.http.HttpHeaders;
|
|
|
|
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
|
|
import org.apache.http.client.methods.HttpGet;
|
|
|
|
|
import org.apache.http.client.methods.HttpPost;
|
|
|
|
|
import org.apache.http.entity.ContentType;
|
|
|
|
|
import org.apache.http.entity.StringEntity;
|
|
|
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
|
|
import org.apache.http.util.EntityUtils;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.security.PrivateKey;
|
|
|
|
|
import java.security.Signature;
|
|
|
|
|
import java.security.cert.X509Certificate;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.Base64;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 微信支付工具类,提供微信支付相关的操作,如下单、退款等。
|
|
|
|
|
*/
|
|
|
|
|
@Component
|
|
|
|
|
public class WeChatPayUtil {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 微信支付下单接口地址。
|
|
|
|
|
*/
|
|
|
|
|
public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 申请退款接口地址。
|
|
|
|
|
*/
|
|
|
|
|
public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private WeChatProperties weChatProperties;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取调用微信接口的客户端工具对象。
|
|
|
|
|
* 使用商户API私钥和微信支付平台证书进行签名和验签。
|
|
|
|
|
*
|
|
|
|
|
* @return CloseableHttpClient 客户端工具对象。
|
|
|
|
|
*/
|
|
|
|
|
private CloseableHttpClient getClient() {
|
|
|
|
|
PrivateKey merchantPrivateKey = null;
|
|
|
|
|
try {
|
|
|
|
|
// 加载商户API私钥
|
|
|
|
|
merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
|
|
|
|
|
// 加载微信支付平台证书
|
|
|
|
|
X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
|
|
|
|
|
List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);
|
|
|
|
|
|
|
|
|
|
// 构建HttpClient,自动处理签名和验签
|
|
|
|
|
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
|
|
|
|
|
.withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
|
|
|
|
|
.withWechatPay(wechatPayCertificates);
|
|
|
|
|
CloseableHttpClient httpClient = builder.build();
|
|
|
|
|
return httpClient;
|
|
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 发送POST请求到指定URL。
|
|
|
|
|
*
|
|
|
|
|
* @param url 请求的URL地址
|
|
|
|
|
* @param body 请求的JSON体
|
|
|
|
|
* @return 响应结果字符串
|
|
|
|
|
* @throws Exception 可能抛出的异常
|
|
|
|
|
*/
|
|
|
|
|
private String post(String url, String body) throws Exception {
|
|
|
|
|
CloseableHttpClient httpClient = getClient();
|
|
|
|
|
HttpPost httpPost = new HttpPost(url);
|
|
|
|
|
httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
|
|
|
|
|
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
|
|
|
|
|
httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
|
|
|
|
|
httpPost.setEntity(new StringEntity(body, "UTF-8"));
|
|
|
|
|
|
|
|
|
|
CloseableHttpResponse response = httpClient.execute(httpPost);
|
|
|
|
|
try {
|
|
|
|
|
String bodyAsString = EntityUtils.toString(response.getEntity());
|
|
|
|
|
return bodyAsString;
|
|
|
|
|
} finally {
|
|
|
|
|
httpClient.close();
|
|
|
|
|
response.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 发送GET请求到指定URL。
|
|
|
|
|
*
|
|
|
|
|
* @param url 请求的URL地址
|
|
|
|
|
* @return 响应结果字符串
|
|
|
|
|
* @throws Exception 可能抛出的异常
|
|
|
|
|
*/
|
|
|
|
|
private String get(String url) throws Exception {
|
|
|
|
|
CloseableHttpClient httpClient = getClient();
|
|
|
|
|
HttpGet httpGet = new HttpGet(url);
|
|
|
|
|
httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
|
|
|
|
|
httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
|
|
|
|
|
httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
|
|
|
|
|
|
|
|
|
|
CloseableHttpResponse response = httpClient.execute(httpGet);
|
|
|
|
|
try {
|
|
|
|
|
String bodyAsString = EntityUtils.toString(response.getEntity());
|
|
|
|
|
return bodyAsString;
|
|
|
|
|
} finally {
|
|
|
|
|
httpClient.close();
|
|
|
|
|
response.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用JSAPI进行微信支付下单。
|
|
|
|
|
*
|
|
|
|
|
* @param orderNum 商户订单号
|
|
|
|
|
* @param total 总金额(元)
|
|
|
|
|
* @param description 商品描述
|
|
|
|
|
* @param openid 微信用户的openid
|
|
|
|
|
* @return 响应结果字符串
|
|
|
|
|
* @throws Exception 可能抛出的异常
|
|
|
|
|
*/
|
|
|
|
|
private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
|
|
|
|
|
JSONObject jsonObject = new JSONObject();
|
|
|
|
|
jsonObject.put("appid", weChatProperties.getAppid());
|
|
|
|
|
jsonObject.put("mchid", weChatProperties.getMchid());
|
|
|
|
|
jsonObject.put("description", description);
|
|
|
|
|
jsonObject.put("out_trade_no", orderNum);
|
|
|
|
|
jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
|
|
|
|
|
|
|
|
|
|
JSONObject amount = new JSONObject();
|
|
|
|
|
amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
|
|
|
|
|
amount.put("currency", "CNY");
|
|
|
|
|
|
|
|
|
|
jsonObject.put("amount", amount);
|
|
|
|
|
|
|
|
|
|
JSONObject payer = new JSONObject();
|
|
|
|
|
payer.put("openid", openid);
|
|
|
|
|
|
|
|
|
|
jsonObject.put("payer", payer);
|
|
|
|
|
|
|
|
|
|
String body = jsonObject.toJSONString();
|
|
|
|
|
return post(JSAPI, body);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 小程序支付接口。
|
|
|
|
|
*
|
|
|
|
|
* @param orderNum 商户订单号
|
|
|
|
|
* @param total 金额,单位元
|
|
|
|
|
* @param description 商品描述
|
|
|
|
|
* @param openid 微信用户的openid
|
|
|
|
|
* @return 包含支付参数的JSONObject
|
|
|
|
|
* @throws Exception 可能抛出的异常
|
|
|
|
|
*/
|
|
|
|
|
public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
|
|
|
|
|
String bodyAsString = jsapi(orderNum, total, description, openid);
|
|
|
|
|
JSONObject jsonObject = JSON.parseObject(bodyAsString);
|
|
|
|
|
String prepayId = jsonObject.getString("prepay_id");
|
|
|
|
|
if (prepayId != null) {
|
|
|
|
|
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
|
|
|
|
|
String nonceStr = RandomStringUtils.randomNumeric(32);
|
|
|
|
|
ArrayList<Object> list = new ArrayList<>();
|
|
|
|
|
list.add(weChatProperties.getAppid());
|
|
|
|
|
list.add(timeStamp);
|
|
|
|
|
list.add(nonceStr);
|
|
|
|
|
list.add("prepay_id=" + prepayId);
|
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
|
for (Object o : list) {
|
|
|
|
|
stringBuilder.append(o).append("\n");
|
|
|
|
|
}
|
|
|
|
|
String signMessage = stringBuilder.toString();
|
|
|
|
|
byte[] message = signMessage.getBytes();
|
|
|
|
|
Signature signature = Signature.getInstance("SHA256withRSA");
|
|
|
|
|
signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
|
|
|
|
|
signature.update(message);
|
|
|
|
|
String packageSign = Base64.getEncoder().encodeToString(signature.sign());
|
|
|
|
|
|
|
|
|
|
JSONObject jo = new JSONObject();
|
|
|
|
|
jo.put("timeStamp", timeStamp);
|
|
|
|
|
jo.put("nonceStr", nonceStr);
|
|
|
|
|
jo.put("package", "prepay_id=" + prepayId);
|
|
|
|
|
jo.put("signType", "RSA");
|
|
|
|
|
jo.put("paySign", packageSign);
|
|
|
|
|
|
|
|
|
|
return jo;
|
|
|
|
|
}
|
|
|
|
|
return jsonObject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 申请退款接口。
|
|
|
|
|
*
|
|
|
|
|
* @param outTradeNo 商户订单号
|
|
|
|
|
* @param outRefundNo 商户退款单号
|
|
|
|
|
* @param refund 退款金额
|
|
|
|
|
* @param total 原订单金额
|
|
|
|
|
* @return 响应结果字符串
|
|
|
|
|
* @throws Exception 可能抛出的异常
|
|
|
|
|
*/
|
|
|
|
|
public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
|
|
|
|
|
JSONObject jsonObject = new JSONObject();
|
|
|
|
|
jsonObject.put("out_trade_no", outTradeNo);
|
|
|
|
|
jsonObject.put("out_refund_no", outRefundNo);
|
|
|
|
|
|
|
|
|
|
JSONObject amount = new JSONObject();
|
|
|
|
|
amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
|
|
|
|
|
amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
|
|
|
|
|
amount.put("currency", "CNY");
|
|
|
|
|
|
|
|
|
|
jsonObject.put("amount", amount);
|
|
|
|
|
jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());
|
|
|
|
|
|
|
|
|
|
String body = jsonObject.toJSONString();
|
|
|
|
|
|
|
|
|
|
return post(REFUNDS, body);
|
|
|
|
|
}
|
|
|
|
|
}
|