一、配置
1、开发文档链接
v2版本官方链接:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
2、公众号后台配置
1、
2、
3、
二、流程
商户系统和微信支付系统主要交互:
1、商户server调用统一下单接口请求订单,api参见公共api【统一下单API】
2、商户server可通过【JSAPI调起支付API】调起微信支付,发起支付请求。
3、商户server接收支付通知,api参见公共api【支付结果通知API】
4、商户server查询支付结果,api参见公共api【查询订单API】(查单实现可参考:支付回调和查单实现指引)
三、代码
v2版本代码可以直接复制使用
1、添加Maven
<!-- 微信支付 SDK -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
2、统一下单接口
@GetMapping("/place")
public AjaxResult place(PayVo payVo) {
System.out.println("money:"+money.toString());
System.out.println("openid:"+payVo.getOpenid());
Map<String,String> weChatPayMap = new HashMap<>();
String wechat_appid = payConfig.getAppid();//小程序appid 或者 公众号appid, 必须与商户绑定
String wechat_mchid = payConfig.getMch_id(); //商户号id
String wechat_seckey = payConfig.getApi_key();//商户秘钥
String out_trade_no = UUIDUtils.getUUID36(); //订单号
String ip = payConfig.getIp();
String openid = payVo.getOpenid();
String trade_type = "JSAPI";
Double d = new Double(money.toString());
Double a=d*100;
String total_fee =a.intValue()+"";
try {
// YmyXlyAct ymyXlyAct = new YmyXlyAct();
// List<YmyXlyAct> list = ymyXlyActService.selectYmyXlyActList(ymyXlyAct);
// String body = qishu;
String product_id = actId;
YmyXlyDingdan XlyDingdan=new YmyXlyDingdan();
XlyDingdan.setOpenid(openid);
XlyDingdan.setMoney(money.toString());
XlyDingdan.setPayType("未支付");
XlyDingdan.setProductId(product_id);
List<YmyXlyDingdan> dingdans= ymyXlyDingdanService.selectYmyXlyDingdanList(XlyDingdan);
if (StringUtils.isNotEmpty(dingdans)){
// JSONObject jsonObject = JSON.parseObject(dingdans.get(0).getParam());
/*JSON对象转map常用*/
Map<String,Object> jsonToMap = JSONObject.parseObject(dingdans.get(0).getParam());
return AjaxResult.success(jsonToMap);
}
//微信统一下单
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", wechat_appid);
paraMap.put("mch_id", wechat_mchid);
String new_body=new String(body.getBytes(),"utf-8");
paraMap.put("body", body);//商品描述
paraMap.put("product_id", product_id);//商品id
paraMap.put("nonce_str", UUIDUtils.getUUID36());
paraMap.put("out_trade_no", out_trade_no);//订单号
paraMap.put("spbill_create_ip", ip);
paraMap.put("total_fee", total_fee);
paraMap.put("trade_type", trade_type);//交易类型 JSAPI支付
paraMap.put("notify_url", WeChatConstants.NOTIFY_URL);// 此路径是微信服务器调用支付结果通知路径随意写,---回调地址
//生成签名, 统一下单
if (StringUtils.equals(WeChatConstants.TRADE_TYPE_JSAPI, trade_type)) {
paraMap.put("openid", openid);//trade_type=JSAPI时(即JSAPI支付),此参数必传
weChatPayMap = WXPayUtil.unifiedorderJSAPI(paraMap, wechat_appid, wechat_seckey, out_trade_no);
System.out.println(weChatPayMap);
if ("200".equals(weChatPayMap.get("paycode"))){
YmyXlyDingdan ymyXlyDingdan=new YmyXlyDingdan();
ymyXlyDingdan.setOpenid(openid);
ymyXlyDingdan.setOutTradeNo(out_trade_no);
ymyXlyDingdan.setPayType("待支付");
ymyXlyDingdan.setMoney(money.toString());
ymyXlyDingdan.setProductId(product_id);
ymyXlyDingdan.setBody(body);
JSONObject JSONObj = JSONObject.parseObject(JSON.toJSONString(weChatPayMap));
ymyXlyDingdan.setParam(String.valueOf(weChatPayMap));
String str = JSONObj.toString();
ymyXlyDingdan.setParam(str);
ymyXlyDingdanService.insertYmyXlyDingdan(ymyXlyDingdan);
}
return AjaxResult.success(weChatPayMap);
}
} catch (Exception e) {
e.printStackTrace();
}
return AjaxResult.success(weChatPayMap);
}
3、查询订单
/**
* 订单支付状态查询
*
* @param wechat_appid
* @param wechat_mchid
* @param wechat_seckey
* @param out_trade_no
* @return
* @throws Exception
*/
@GetMapping("/orderquery")
public Map<String, String> orderquery(String wechat_appid, String wechat_mchid, String wechat_seckey, String out_trade_no) throws Exception {
//拼接 参数
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", wechat_appid);
paraMap.put("mch_id", wechat_mchid);
paraMap.put("nonce_str", UUIDUtils.getUUID36());
paraMap.put("out_trade_no", out_trade_no);//订单号
String sign = WXPayUtil.generateSignature(paraMap, wechat_seckey);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
String xmlStr = HttpUtils.sendPost(WeChatConstants.ORDERQUERY_URL, xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
if(map!=null && "SUCCESS".equalsIgnoreCase(map.get("trade_state"))){
//支付成功
//2.修改订单状态为“待发货/已支付”
String orderId = out_trade_no;
// String orderId = map.get("out_trade_no");
// int i =1;
YmyXlyDingdan ymyXlyDingdan = new YmyXlyDingdan();
ymyXlyDingdan.setOutTradeNo(orderId);
ymyXlyDingdan.setPayType("已支付");
ymyXlyDingdanService.updateYmyXlyDingdan(ymyXlyDingdan);
List<YmyXlyDingdan> list = ymyXlyDingdanService.selectYmyXlyDingdanList(ymyXlyDingdan);
String[] actIds=list.get(0).getProductId().split(",");
for (String actId:actIds){
YmyXly ymyXly = new YmyXly();
ymyXly.setOpenid(list.get(0).getOpenid());
ymyXly.setActId(Long.valueOf(actId));
ymyXly.setMoney(list.get(0).getMoney());
List<YmyXly> ymyXlies = ymyXlyService.selectYmyXlyList(ymyXly);
YmyXly xly=new YmyXly();
BeanUtils.copyBeanProp(xly,ymyXlies.get(0));
xly.setIsPay("已付款");
ymyXlyService.updateYmyXly(xly);
}
//3.响应微信支付平台
}
return map;
}
4、下单回调接口
/**
* 1.接收微信支付平台传递的数据(使用request的输入流接收)
*/
@PostMapping("/callback")
public String success(HttpServletRequest request) throws Exception {
System.out.println("进入回调----------------------------------------------------");
ServletInputStream is = request.getInputStream();
byte[] bs = new byte[1024];
int len = -1;
StringBuilder builder = new StringBuilder();
while ((len = is.read(bs)) != -1) {
builder.append(new String(bs, 0, len));
}
String s = builder.toString();
System.out.println("s="+s);
//使用帮助类将xml接口的字符串转换成map
Map<String, String> map = WXPayUtil.xmlToMap(s);
System.out.println("map:"+map);
if(map!=null && "SUCCESS".equalsIgnoreCase(map.get("return_code"))){
//支付成功
//2.修改订单状态为“待发货/已支付”
String orderId = map.get("out_trade_no");
// int i =1;
try {
YmyXlyDingdan ymyXlyDingdan = new YmyXlyDingdan();
ymyXlyDingdan.setOutTradeNo(orderId);
ymyXlyDingdan.setPayType("已支付");
int i = ymyXlyDingdanService.updateYmyXlyDingdan(ymyXlyDingdan);
List<YmyXlyDingdan> list = ymyXlyDingdanService.selectYmyXlyDingdanList(ymyXlyDingdan);
String[] actIds=list.get(0).getProductId().split(",");
for (String actId:actIds){
YmyXly ymyXly = new YmyXly();
ymyXly.setOpenid(list.get(0).getOpenid());
ymyXly.setActId(Long.valueOf(actId));
ymyXly.setMoney(list.get(0).getMoney());
List<YmyXly> ymyXlies = ymyXlyService.selectYmyXlyList(ymyXly);
YmyXly xly=new YmyXly();
BeanUtils.copyBeanProp(xly,ymyXlies.get(0));
xly.setIsPay("已付款");
ymyXlyService.updateYmyXly(xly);
}
System.out.println("--orderId:" + orderId);
//3.响应微信支付平台
if (i > 0) {
HashMap<String, String> resp = new HashMap<>();
resp.put("return_code", "success");
resp.put("return_msg", "OK");
resp.put("appid", map.get("appid"));
resp.put("result_code", "success");
return WXPayUtil.mapToXml(resp);
}
}catch (Exception e){
e.printStackTrace();
}
}
//支付失败
return null;
}
5、工具类util
1、HttpUtils
import com.alibaba.fastjson2.JSONObject;
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.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.*;
import java.net.ConnectException;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyStore;
public class HttpUtils {
public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
StringBuffer buffer = new StringBuffer();
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
if ("GET".equalsIgnoreCase(requestMethod))
httpUrlConn.connect();
// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
jsonObject = JSONObject.parseObject(buffer.toString());
return jsonObject;
} catch (ConnectException ce) {
System.out.println("Weixin server connection timed out.");
} catch (Exception e) {
System.out.println("error."+ e.getMessage());
}
return jsonObject;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("Charsert", "UTF-8");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
/**
* 需要使用证书请求接口
*/
public static String requestWithCert(String url,String pay_cert,String mchid, String data) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream is = new FileInputStream(new File(pay_cert));
try {
keyStore.load(is, mchid.toCharArray());// 这里写密码..默认是你的MCHID
} finally {
is.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchid.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();
}
}
}
2、WeChatConstants
/**
* 常量
* @author voayc
*
*/
public class WeChatConstants {
public enum SignType {
MD5, HMACSHA256
}
public static final String FIELD_SIGN = "sign";
/** 支付成功回调地址 */
public static String NOTIFY_URL= "http://8.142.116.233:8001/pay/wxpay/callback";微信支付回调地址
/** 支付证书路径 */
public static String PAY_CERT_LOC = "C:\\CTO\\java\\apache-tomcat-8.0.50\\wxcert\\";
/** 微信 trade_type 参数 */
public static final String TRADE_TYPE_JSAPI = "JSAPI";//JSAPI支付 例如 : 直接调用微信支付
public static final String TRADE_TYPE_NATIVE = "NATIVE";//Native支付 例如 : 扫码支付
/**
* 统一下单
*/
public static String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 订单支付状态查询
*/
public static String ORDERQUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
/**
* 退款
*/
public static String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
/**
* 提现
*/
public static String TRANSFERS_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
}
3、WXPayUtil
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.sign.Md5Utils;
import com.ruoyi.garden.controller.wxpay.constants.WeChatConstants;
import com.ruoyi.garden.controller.wxpay.dto.*;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* JSAPI 下单
* @Title: unifiedorderJSAPI
* @param paraMap
* @param wechat_seckey
* @return
* @author: voayc
* @date: 2020年3月17日 上午10:00:49
*/
public static Map<String,String> unifiedorderJSAPI(Map<String, String> paraMap, String wechat_appid, String wechat_seckey, String out_trade_no) throws Exception {
Map<String,String> weChatPayMap = new HashMap<>();
//生成签名, 统一下单
System.out.println("paraMap------------"+paraMap);
String sign = WXPayUtil.generateSignature(paraMap, wechat_seckey);
System.out.println("sign:"+sign);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
String new_xml=new String(xml.getBytes("utf-8"));
System.out.println("xml:"+new_xml);
String xmlStr = HttpUtils.sendPost(WeChatConstants.UNIFIEDORDER_URL, new_xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
System.out.println("xmlStr:"+xmlStr);
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
//以下内容是返回前端页面的json数据
if (xmlStr.indexOf("SUCCESS") == -1) {
weChatPayMap.put("message",CommonText.UNIFIEDORDER_ERR+":"+map + ",paraMap:"+paraMap +",return_msg" + map.get("return_msg") +",err_code_des" + map.get("err_code_des"));
// b.setDefultFailInfo();
// b.setMessage(CommonText.UNIFIEDORDER_ERR+":"+map + ",paraMap:"+paraMap +",return_msg" + map.get("return_msg") +",err_code_des" + map.get("err_code_des"));
//
return weChatPayMap;
}
//返回前台参数
Map<String,String> payMap = new HashMap<>();
String prepay_id = MapUtils.getString(map, "prepay_id");
if (StringUtils.isBlank(prepay_id)) {
weChatPayMap.put("message",CommonText.UNIFIEDORDER_ERR_PREPAY_ID_NOT_FIND + "," + map.get("err_code_des"));
// b.setDefultFailInfo();
// b.setMessage(CommonText.UNIFIEDORDER_ERR_PREPAY_ID_NOT_FIND + "," + map.get("err_code_des"));
return weChatPayMap;
}
weChatPayMap.put("appId", wechat_appid);
weChatPayMap.put("timeStamp", WXPayUtil.getCurrentTimestamp()+"");
weChatPayMap.put("nonceStr", WXPayUtil.generateNonceStr());
weChatPayMap.put("signType", "MD5");
weChatPayMap.put("package", "prepay_id=" + prepay_id);
String paySign = WXPayUtil.generateSignature(weChatPayMap, wechat_seckey);
weChatPayMap.put("paySign", paySign);
weChatPayMap.put("out_trade_no", out_trade_no);
weChatPayMap.put("paycode", "200");
// b.put("data",payMap);
return weChatPayMap;
}
/**
* NATIVE 下单
* @Title: unifiedorderJSAPI
* @param paraMap
* @param wechat_seckey
* @return
* @author: voayc
* @date: 2020年3月17日 上午10:00:49
*/
public static BasicResponse unifiedorderNATIVE(Map<String, String> paraMap,String wechat_appid, String wechat_seckey,String out_trade_no) throws Exception {
BasicResponse b = new BasicResponse<>();
//生成签名, 统一下单
String sign = WXPayUtil.generateSignature(paraMap, wechat_seckey);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
String xmlStr = HttpUtils.sendPost(WeChatConstants.UNIFIEDORDER_URL, xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
//以下内容是返回前端页面的json数据
if (xmlStr.indexOf("SUCCESS") == -1) {
b.setDefultFailInfo();
b.setMessage(CommonText.UNIFIEDORDER_ERR+":"+map + ",paraMap:"+paraMap +",return_msg" + map.get("return_msg"));
return b;
}
//将未支付状态 放入redis缓存, 用于前端循环查询, 扫码支付是否成功
//RedisClientUtils.save(SystemConstant.WECHAT_PAY_NATIVE_STATE+out_trade_no, "0",SystemConstant.WECHAT_PAY_NATIVE_STATE_EXPIRE);
Map<String,String> resultMap = new HashMap<>();
resultMap.put("code_url", MapUtils.getString(map, "code_url"));
resultMap.put("out_trade_no", out_trade_no);
b.setObject(resultMap);
b.setMessage(map.get("err_code_des"));
return b;
}
/**
* 微信退款
* @Title: wxRefund
* @param wechat_appid 公众号/小程序 appid
* @param wechat_mchid 微信支付商户号
* @param wechat_seckey 微信支付商户号
* @param wechat_pay_cert 微信支付商户号证书路径
* @param out_trade_no 订单编码
* @param service_code 售后编码
* @param total_price 订单总金额
* @param refund_fee 退款金额
* @author: voayc
* @throws Exception
* @date: 2020年4月18日 下午4:53:58
*/
public static BasicResponse wxRefund(String wechat_appid, String wechat_mchid, String wechat_seckey, String wechat_pay_cert, String out_trade_no, String service_code, BigDecimal total_price, BigDecimal refund_fee) throws Exception {
BasicResponse b = new BasicResponse();
//拼接 退款 参数
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", wechat_appid);
paraMap.put("mch_id", wechat_mchid);
paraMap.put("nonce_str", UUIDUtils.getUUID36());
paraMap.put("out_trade_no", out_trade_no);//订单号
paraMap.put("out_refund_no", service_code);//商户退款单号, 必须为不同, 相同的话, 只会退一笔
paraMap.put("total_fee",total_price.multiply(new BigDecimal("100")).setScale(0).toString());//单位为分
paraMap.put("refund_fee",refund_fee.multiply(new BigDecimal("100")).setScale(0).toString());//单位为分
String sign = WXPayUtil.generateSignature(paraMap, wechat_seckey);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
//退款
String xmlStr = HttpUtils.requestWithCert(WeChatConstants.REFUND_URL,(WeChatConstants.PAY_CERT_LOC+wechat_pay_cert),wechat_mchid, xml);
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
if (xmlStr.indexOf("SUCCESS") == -1) {
b.setDefultFailInfo();
b.setMessage(CommonText.UNIFIEDORDER_ERR+":"+map + ",paraMap:"+paraMap +",return_msg" + map.get("return_msg"));
return b;
}
return b;
}
/**
* 微信提现
* @Title: wxTransfers
* @param wechat_appid 公众号/小程序 appid
* @param wechat_mchid 微信支付商户号
* @param wechat_seckey 微信支付商户号
* @param wechat_pay_cert 微信支付商户号证书路径
* @param out_trade_no 订单编码
* @param openid openid
* @param transfers_price 提现金额
* @author: voayc
* @throws Exception
* @date: 2020年4月18日 下午4:53:58
*/
public static BasicResponse wxTransfers(String wechat_appid, String wechat_mchid, String wechat_seckey, String wechat_pay_cert, String out_trade_no, String openid, BigDecimal transfers_price) throws Exception {
BasicResponse b = new BasicResponse();
//拼接 退款 参数
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("mch_appid", wechat_appid);
paraMap.put("mchid", wechat_mchid);
paraMap.put("nonce_str", UUIDUtils.getUUID36());
paraMap.put("partner_trade_no", out_trade_no);//订单号
paraMap.put("openid", openid);//退款会员id
paraMap.put("check_name", "NO_CHECK");//NO_CHECK:不校验真实姓名
paraMap.put("desc", "提现");//企业付款备注,必填
paraMap.put("amount",transfers_price.multiply(new BigDecimal("100")).setScale(0).toString());//单位为分
String sign = WXPayUtil.generateSignature(paraMap, wechat_seckey);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
//提现
String xmlStr = HttpUtils.requestWithCert(WeChatConstants.TRANSFERS_URL,(WeChatConstants.PAY_CERT_LOC+wechat_pay_cert),wechat_mchid, xml);
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
String return_code = MapUtils.getString(map, "return_code");
String result_code = MapUtils.getString(map, "result_code");
if ( !(StringUtils.equals("SUCCESS", return_code) && StringUtils.equals("SUCCESS", result_code)) ) {
b.setDefultFailInfo();
b.setMessage(CommonText.UNIFIEDORDER_ERR+":"+map + ",paraMap:"+paraMap +",return_msg" + map.get("return_msg"));
return b;
}
return b;
}
/**
* 订单支付状态查询
* @param wechat_appid
* @param wechat_mchid
* @param wechat_seckey
* @param out_trade_no
* @return
* @throws Exception
*/
public static Map<String, String> orderquery(String wechat_appid, String wechat_mchid, String wechat_seckey, String out_trade_no) throws Exception {
//拼接 参数
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", wechat_appid);
paraMap.put("mch_id", wechat_mchid);
paraMap.put("nonce_str", UUIDUtils.getUUID36());
paraMap.put("out_trade_no", out_trade_no);//订单号
String sign = WXPayUtil.generateSignature(paraMap, wechat_seckey);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
String xmlStr = HttpUtils.sendPost(WeChatConstants.ORDERQUERY_URL, xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
return map;
}
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
// InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
InputStream stream = new ByteArrayInputStream(strXML.getBytes("GBK"));
Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
Document document = WXPayXmlUtil.newDocument();
Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "GBK");
// transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, WeChatConstants.SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, WeChatConstants.SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WeChatConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WeChatConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WeChatConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, WeChatConstants.SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, WeChatConstants.SignType signType) throws Exception {
if (!data.containsKey(WeChatConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WeChatConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, WeChatConstants.SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, WeChatConstants.SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WeChatConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (WeChatConstants.SignType.MD5.equals(signType)) {
// return Md5Utils.hash(sb.toString());
return MD5(sb.toString()).toUpperCase();
}
else if (WeChatConstants.SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
}
4、WXPayXmlUtil
package com.ruoyi.garden.controller.wxpay.dto;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
public final class WXPayXmlUtil {
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}
5、MyX509TrustManager
package com.ruoyi.garden.controller.wxpay.dto;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* 对于https请求,我们需要一个证书信任管理器,这个管理器类需要自己定义,但需要实现X509TrustManager接口
* 证书信任管理器(用于https请求)
* 这个证书管理器的作用就是让它信任我们指定的证书,上面的代码意味着信任所有证书,不管是否权威机构颁发。
*
* @author jiangyin
*/
public class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
6、BasicResponse
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
/**
* 功能描述:基础交互对象
*/
public class BasicResponse<T> implements Serializable{
/** 描述 (@author: yangxg) */
private static final long serialVersionUID = 1L;
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private boolean success = true;
private T object;
private String message = "";
private String code = "200";
private String remark = "";
private String time;//服务器返回时间
private String addition = "";//附加信息
//系统状态码
public static final String STATUS_CODE_FAIL_300 = "300";//token失效
public static final String STATUS_CODE_FAIL_301 = "301";//未上传sys_amdin_id
public static final String STATUS_CODE_FAIL_400 = "400";//错误请求,如语法错误
public static final String STATUS_CODE_FAIL_401 = "401";//未登录
public static final String STATUS_CODE_FAIL_404 = "404";//没有发现文件、查询或URl
public static final String STATUS_CODE_FAIL_500 = "500";//服务器产生内部错误
public static final String STATUS_CODE_FAIL_501 = "501";//服务器不支持请求的函数
public static final String SUCCESS = "success";
public static final String MESSAGE = "message";
public static final String REMARK = "remark";
public static final String ADDITION = "addition";
public static final String TIME = "time";
public static final String CODE = "code";
@SuppressWarnings("unchecked")
public BasicResponse() {
this.success = true;
this.object = (T)new HashMap<String,Object>();
this.time = sdf.format(new Date());
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public T getObject() {
return object;
}
public BasicResponse setObject(T object) {
this.object = object;
return this;
}
public String getMessage() {
return message;
}
public BasicResponse setMessage(String message) {
this.message = message;
return this;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public void setDefultSuccesInfo() {
this.message = "OPT SUCCESS!";
this.code = "200";
this.success = true;
}
public BasicResponse setDefultFailInfo() {
return this.setDefultFailInfo(StringUtils.EMPTY);
}
public BasicResponse setDefultFailInfo(String msg) {
this.message = StringUtils.isBlank(msg)?"OPT FAILED!" : msg;
this.code = BasicResponse.STATUS_CODE_FAIL_400;
this.success = false;
return this;
}
public void setFailInfo501() {
this.message = "user not exist!";
this.code = BasicResponse.STATUS_CODE_FAIL_501;
this.success = false;
}
public void SETUseNotlogin() {
this.message = "user not login!";
this.code = BasicResponse.STATUS_CODE_FAIL_401;
this.success = false;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@SuppressWarnings("unchecked")
public void setDefultObject() {
this.object = (T) new HashMap<String,Object>();
}
public String getAddition() {
return addition;
}
public void setAddition(String addition) {
this.addition = addition;
}
@Override
public String toString() {
return "BasicResponse [success=" + success + ", object=" + object
+ ", message=" + message + ", code=" + code + ", remark="
+ remark + ", time=" + time + ", addition=" + addition + "]";
}
public static String getNowTime() {
String time = sdf.format(new Date());
return time;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public static BasicResponse buildNew(){
return new BasicResponse();
}
}
7、CommonText
/**
* 功能描述:系统提示 使用方法: create by voayc
*/
public class CommonText {
public static final String DATA_NOT_EXIST = "数据不存在";
/** 微信公众号*/
public static final String WECHAT_CONFIG_DOES_NOT_EXIST = "未配置公司微信公众号的相关信息";
public static final String WECHAT_PAY_CONFIG_DOES_NOT_EXIST = "未配置公司微信支付的相关信息";
public static final String OPENID_DOES_NOT_EXIST = "请上传微信用户openid";
public static final String WECHAT_TRADE_TYPE_DOES_NOT_EXIST = "请上传微信支付交易类型";
public static final String OPENID_ALREADY_BIND = "该微信号已被其他账号绑定!";
public static final String ADMIN_OPENID_DOES_NOT_EXIST = "请上传管理员openid";
public static final String OPENID_DOES_NOT_FIND = "未获取到对应微信用户openid";
public static final String WECHAT_CODE_DOES_NOT_EXIST = "请上传微信用户code";
public static final String WECHAT_USER_TOKEN_DOES_ERR = "获取微信用户token失败";
public static final String UNIFIEDORDER_ERR = "统一下单失败";
public static final String UNIFIEDORDER_ERR_PREPAY_ID_NOT_FIND = "统一下单失败,未生成PREPAY_ID";
/** 微信小程序*/
public static final String MINI_OPENID_DOES_NOT_EXIST = "请上传小程序用户openid";
public static final String WECHAT_MINI_CONFIG_DOES_NOT_EXIST = "未配置公司微信小程序的相关信息";
public static final String WECHAT_MINI_PARAM_NOT_EXIST = "缺少所需小程序参数!";
public static final String WECHAT_MINI_IV_NOT_EXIST = "请上传iv参数";
public static final String WECHAT_MINI_SESSION_KEY_DOES_NOT_EXIST = "请上传session_key参数";
public static final String WECHAT_MINI_ENCRYPTED_DATA_DOES_NOT_EXIST = "请上传encrypted_data参数";
public static final String WECHAT_MINI_ENCRYPTED_DATA_PARSE_ERR = "encrypted_data解析失败";
public static final String WECHAT_MINI_ENCRYPTED_DATA_NOT_HAVE_PHONE_NUM = "encrypted_data中未解析出电话号码";
/** 支付相关 */
public static final String ORDER_PAY_TYPE_NOT_EXIST = "请上传支付方式!";
}
8、UUIDUtils
import java.util.UUID;
/**
* 功能描述:uuid工具类
* 使用方法:
*/
public class UUIDUtils {
/**
* 功能描述:获取uuid不带"-"
* 使用方法:
* 使用约束:
* 逻辑:
* @return
* create voayc 2017年9月4日 下午6:34:44
*/
public static String getUUID36() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 功能描述:获取原始uuid
* 使用方法:
* 使用约束:
* 逻辑:
* @return
* create voayc 2017年9月4日 下午6:34:34
*/
public static String getUUID36OLD() {
return UUID.randomUUID().toString();
}
}