微信退款功能 需要证书
微信开放文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
代码中 keyStore.load(is, PayUtil.MCH_ID.toCharArray()); 会报错
报错信息:DerInputStream.getLength(): lengthTag=111, too big.
在pom.xml(parent)中加入如下配置,过滤后缀为pkcs12、jks,p12的证书文件。如果还加载其他文件,可以自行加入
<!--解决 DerInputStream.getLength(): lengthTag=111, too big. BEGIN-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration><encoding>UTF-8</encoding>
<!-- 过滤后缀为pem、pfx,p12的证书文件 -->
<nonFilteredFileExtensions>
<nonFilteredFileExtension>cer</nonFilteredFileExtension>
<nonFilteredFileExtension>pem</nonFilteredFileExtension>
<nonFilteredFileExtension>pfx</nonFilteredFileExtension>
<nonFilteredFileExtension>p12</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
<!--解决 DerInputStream.getLength(): lengthTag=111, too big. END-->
package io.geekidea.springbootplus.system.util;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Value;
import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.util.*;
public class PayUtil {
private static String APPID = ""; //小程序的appid
private static String MCH_ID = ""; //商户号
private static String KEY = ""; //商户秘钥
private byte [] certData;
@Value("${}")
private String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
private static String mapToXml(Map<String, String> param) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (Map.Entry<String, String> entry : param.entrySet()) {
sb.append("<" + entry.getKey() + ">");
sb.append(entry.getValue());
sb.append("</" + entry.getKey() + ">");
}
sb.append("</xml>");
return sb.toString();
}
private static Map xmlToMap(String strxml) throws Exception {
Map<String, String> map = new HashMap<>();
if (null == strxml || "".equals(strxml)) {
return null;
}
InputStream in = String2Inputstream(strxml);
SAXReader read = new SAXReader();
Document doc = read.read(in);
//得到xml根元素
Element root = doc.getRootElement();
//遍历 得到根元素的所有子节点
@SuppressWarnings("unchecked")
List<Element> list = root.elements();
for (Element element : list) {
//装进map
map.put(element.getName(), element.getText());
}
//关闭流
in.close();
return map;
}
private static InputStream String2Inputstream(String strxml) throws IOException {
return new ByteArrayInputStream(strxml.getBytes("UTF-8"));
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
private static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
String preStr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
preStr = preStr + key + "=" + value;
} else {
preStr = preStr + key + "=" + value + "&";
}
}
return preStr;
}
/**
* 签名字符串
*
* @param text 需要签名的字符串
* @param key 密钥
* @param input_charset 编码格式
* @return 签名结果
*/
public static String sign(String text, String key, String input_charset) {
text = text + "&key=" + key;
return DigestUtils.md5Hex(getContentBytes(text, input_charset));
}
private static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
/**
* 调用微信退款接口
*/
private String doRefund(String url, String data) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12"); //证书格式
try {
InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("static/apiclient_cert.p12");
this.certData = IOUtils.toByteArray(certStream);
certStream.close();
} catch (Exception e) {
e.printStackTrace();
}
ByteArrayInputStream is = new ByteArrayInputStream(this.certData);
try {
keyStore.load(is, PayUtil.MCH_ID.toCharArray());
}catch (Exception e){
e.printStackTrace();
}
finally {
is.close();
}
System.out.println("ceshi----------------->");
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(
keyStore,
PayUtil.MCH_ID.toCharArray())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
HttpPost httpost = new HttpPost(url); // 设置响应头信息
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
/**
* 申请退款
*
* @param orderId 商户订单号
* @param refundId 商户退款单号
* @param totalFee 订单金额
* @param refundFee 退款金额
* @param refundAccount 退款资金来源(默认传 "REFUND_SOURCE_UNSETTLED_FUNDS")
* 注: 退款金额不能大于订单金额
*/
public Map<String, String> refund(String orderId, String refundId, Integer totalFee,
Integer refundFee, String refundAccount) {
Map<String, String> params = new HashMap<>();
params.put("appid", PayUtil.APPID);
params.put("mch_id", PayUtil.MCH_ID);
params.put("nonce_str", getRandomStringByLength(32));
params.put("out_trade_no", orderId); //商户订单号和微信订单号二选一(我这里选的是商户订单号)
params.put("out_refund_no", refundId);//商户退款单号
params.put("total_fee", totalFee.toString());//订单金额
params.put("refund_fee", refundFee.toString());//退款金额
params.put("refund_account", refundAccount); //
params.put("sign_type", "MD5");
String preStr = PayUtil.createLinkString(params);
//签名算法
String sign = (sign(preStr, PayUtil.KEY, "utf-8")).toUpperCase();
params.put("sign", sign);
Map<String, String> map = new HashMap<>();
try {
String xml = mapToXml(params);
String xmlStr = doRefund("https://api.mch.weixin.qq.com/secapi/pay/refund", xml);
map = xmlToMap(xmlStr);
} catch (Exception e) {
}
return map;
}
//
}
以下代码存在签名错误问题 待解决【微信签名验证工具检验通过,代码中不好使】
//企业支付到个人
public Map<String,String> CorporatePayment(String orderId, String openid, Integer totalFee,
String refundAccount){
Map<String, String> params = new HashMap<>();
params.put("amount", totalFee.toString());//订单金额 单位分
params.put("check_name","NO_CHECK");
params.put("desc", refundAccount); //备注 必须
params.put("mch_appid", PayUtil.APPID);
params.put("mchid", PayUtil.MCH_ID);
params.put("nonce_str", getRandomStringByLength(32));
params.put("openid",openid);//用户在此小程序中的唯一标识 appid
params.put("partner_trade_no", orderId); //商户订单号
params.put("sign_type", "MD5");
String preStr = PayUtil.createLinkString(params);
//签名算法
String sign = (sign(preStr, PayUtil.KEY, "utf-8")).toUpperCase();
params.put("sign", sign);
Map<String, String> map = new HashMap<>();
try {
String xml = mapToXml(params);
String xmlStr = doRefund("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers", xml);
map = xmlToMap(xmlStr);
} catch (Exception e) {
}
return map;
}