微信支付配置
@Value("${wx.user_appid}")
public String user_appid;//appId
@Value("${wx.wxmch_id}")
private String wxmch_id;//商户号
@Value("${wx.mchKey}")
private String wxmchKey;//商户平台密钥
@Value("${wx.payResultURL}")
private String payResultURL;//支付返回路径
@Value("${wx.backResultURL}")
private String backResultURL;//退款返回路径
1.支付
1.下单支付
/**
* 微信JSAPI支付--下单支付
*
* @param body 商品描述
* @param total_fee 总金额
* @param openid
* @param request
* @return
* @throws Exception
*/
public JSONObject payByWxPay(String body, int total_fee, String orderNumber, String openid, HttpServletRequest request) throws Exception {
String appid = user_appid;//应用ID
String mch_id = wxmch_id;//商户号
String notify_url = payResultURL;//支付回调接口
String mchKey = wxmchKey;//商户平台密钥
String nonce_str = generateNonceStr();//随机字符串
String spbill_create_ip = RequestTools.getReqestIp(request);//终端IP
String trade_type = "JSAPI";//支付类型
Map<String, String> paramsMap = new HashMap<String, String>();//String转Map
paramsMap.put("appid", appid);//小程序ID
paramsMap.put("mch_id", mch_id);//商户号
paramsMap.put("nonce_str", nonce_str);//随机字符串
paramsMap.put("body", body);//商品描述
paramsMap.put("out_trade_no", orderNumber);//商户订单号
paramsMap.put("total_fee", String.valueOf(total_fee));//总金额
paramsMap.put("spbill_create_ip", spbill_create_ip);//终端IP
paramsMap.put("notify_url", notify_url);//通知地址
paramsMap.put("trade_type", trade_type);//交易类型
paramsMap.put("openid", openid);//用户标识
logger.info("*************paramsMap*******************" + paramsMap);
// paramsMap.put("profit_sharing","Y");//用户标识
String sign = generateSignature(paramsMap, mchKey, WXPayConstants.SignType.MD5);
paramsMap.put("sign", sign);
logger.info("*************sign签名*******************" + sign);
String xml = mapToXml(paramsMap);//将所有参数(map)转xml格式
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String xmlStr = HtmlTools.postXml(url, xml);
logger.info("*************xml*******************" + xml);
logger.info("*************xmlStr*******************" + xmlStr);
Map<String, String> map = null;
if (xmlStr.indexOf("SUCCESS") != -1) {
map = WXPayUtil.xmlToMap(xmlStr);
System.out.println("输出的支付结果====" + map);
Map<String, String> payMap = new HashMap<String, String>();
payMap.put("appId", map.get("appid"));//appid
payMap.put("timeStamp", System.currentTimeMillis() / 1000 + "");//当前时间戳
// payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + "");//当前时间戳
payMap.put("nonceStr", generateNonceStr());//随机字符串
payMap.put("package", "prepay_id=" + map.get("prepay_id"));//统一下单接口返回的prepay_id
payMap.put("signType", "MD5");//签名方式MD5
String paySign = generateSignature(payMap, mchKey, WXPayConstants.SignType.MD5);
payMap.put("paySign", paySign);
String str = JSON.toJSONString(payMap);
JSONObject obj = JSON.parseObject(str);
obj.put("returnMsg", "支付成功");
logger.info("*************obj*******************" + obj);
return obj;
} else {
String returnMsg = "";
logger.info("erro........");
if (xmlStr.indexOf("return_msg") != -1) {
String[] returnMsgArray = xmlStr.split("return_msg");
returnMsg = returnMsgArray[1].substring(10, returnMsgArray[1].indexOf("]]"));
} else {
returnMsg = "错误信息异常";
}
// LOGGER.error("订单" + out_trade_no + "支付异常:" + returnMsg);
JSONObject reObj = new JSONObject();
reObj.put("returnMsg", returnMsg);
return reObj;
}
}
2.支付回调接口
@RequestMapping("/WXsuccess")
@ApiOperation(value = "支付成功回调接口")
public String WXpayOk(HttpServletResponse httpServletResponse) throws IOException {
try {
logger.info("********************进入try回调*************************");
RequestWrapper requestWrapper = new RequestWrapper(request);
logger.info("*************requestWrapper*******************" + requestWrapper);
String requestBody = requestWrapper.getRequestBody(request);
logger.info("*************requestBody*******************" + requestBody);
JSONObject bodyJson = XmlTool.documentToJSONObject(requestBody);
logger.info("*************执行支付的bodyJson*******************" + bodyJson);
logger.info("**********支付时间***************====" + new Date());
//微信支付结果
String result_code = bodyJson.getString("result_code");
String return_code = bodyJson.getString("return_code");
logger.info("微信支付结果result_code****************************************" + result_code);
logger.info("微信支付结果return_code****************************************" + return_code);
//微信订单编号 退款需要记得存储
String MainOrderId = bodyJson.getString("transaction_id");
logger.info("微信订单编号****************************************" + MainOrderId);
//钱
Integer total_fee = bodyJson.getInteger("total_fee");
logger.info("支付的钱111111****************************************" + total_fee);
logger.info("转换后的钱22222****************************************" + total_fee.doubleValue() / 100);
//订单编号
String out_trade_no = bodyJson.getString("out_trade_no");
logger.info("微信订单编号****************************************" + out_trade_no);
} catch (Exception e) {
e.printStackTrace();
logger.error("支付回调异常===" + e);
}
return "<xml>" +
" <return_code><![CDATA[SUCCESS]]></return_code>" +
" <return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
}
package com.ddm.baseService.tools;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 描述:解决RequestBody读取一次后数据丢失问题【来源:https://www.jianshu.com/p/e4d3cca286e4】 <br/>
*/
public class RequestWrapper extends HttpServletRequestWrapper{
private static Logger logger = Logger.getLogger(RequestWrapper.class);
private byte[] body;
private BufferedReader reader;
private ServletInputStream inputStream;
public RequestWrapper(HttpServletRequest request) throws IOException{
super(request);
loadBody(request);
}
private void loadBody(HttpServletRequest request) throws IOException{
body = IOUtils.toByteArray(request.getInputStream());
inputStream = new RequestCachingInputStream(body);
}
public String getRequestBody(ServletRequest request){
try {
return IOUtils.toString(this.getBody(), request.getCharacterEncoding());
} catch (IOException e) {
logger.info("getRequestBody 异常:"+e.getMessage());
}
return null;
}
public byte[] getBody() {
return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (inputStream != null) {
return inputStream;
}
return super.getInputStream();
}
@Override
public BufferedReader getReader() throws IOException {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
}
return reader;
}
private static class RequestCachingInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RequestCachingInputStream(byte[] bytes) {
inputStream = new ByteArrayInputStream(bytes);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readlistener) {
}
}
}
package com.ddm.baseService.tools;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.log4j.Logger;
import org.dom4j.*;
import java.util.List;
/**
* xml工具类
*
* @author sleep
* @date 2016-09-13
*/
public class XmlTool {
private static Logger logger = Logger.getLogger(XmlTool.class);
/**
* String 转 org.dom4j.Document
*
* @param xml
* @return
* @throws DocumentException
*/
public static Document strToDocument(String xml) throws DocumentException {
return DocumentHelper.parseText(xml);
}
/**
* org.dom4j.Document 转 com.alibaba.fastjson.JSONObject
*
* @param xml
* @return
* @throws DocumentException
*/
public static JSONObject documentToJSONObject(String xml) throws DocumentException {
return elementToJSONObject(strToDocument(xml).getRootElement());
}
/**
* org.dom4j.Element 转 com.alibaba.fastjson.JSONObject
*
* @param node
* @return
*/
public static JSONObject elementToJSONObject(Element node) {
JSONObject result = new JSONObject();
// 当前节点的名称、文本内容和属性
List<Attribute> listAttr = node.attributes();// 当前节点的所有属性的list
for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
result.put(attr.getName(), attr.getValue());
}
// 递归遍历当前节点所有的子节点
List<Element> listElement = node.elements();// 所有一级子节点的list
if (!listElement.isEmpty()) {
for (Element e : listElement) {// 遍历所有一级子节点
if (e.attributes().isEmpty() && e.elements().isEmpty()) // 判断一级节点是否有属性和子节点
result.put(e.getName(), e.getTextTrim());// 沒有则将当前节点作为上级节点的属性对待
else {
if (!result.containsKey(e.getName())) // 判断父节点是否存在该一级节点名称的属性
result.put(e.getName(), new JSONArray());// 没有则创建
((JSONArray) result.get(e.getName())).add(elementToJSONObject(e));// 将该一级节点放入该节点名称的属性对应的值中
}
}
}
return result;
}
public static void main(String[] args) throws Exception {
logger.info(documentToJSONObject("<xml><ToUserName><![CDATA[gh_b426595b0475]]></ToUserName>" +
"<FromUserName><![CDATA[o9Mm208CGUJh8CpGnFUfoEck4XoQ]]></FromUserName>" +
"<CreateTime>1542266839</CreateTime>" +
"<MsgType><![CDATA[text]]></MsgType>" +
"<Content><![CDATA[。。。]]></Content>" +
"<MsgId>6623985635807987968</MsgId>" +
"</xml>"));
}
}
.退款
1.退款
微信退款---下单支付退款
/**
* 微信退款---下单支付退款
*
* @param totalFee 订单金额
* @param refundFee 退款金额
*/
public JSONObject backByWxPay(String orderNumber, String transaction_id, double totalFee, double refundFee, HttpServletRequest request) {
JSONObject reObj = new JSONObject();
logger.info("退款接口。。。。。。");
// new Thread(() -> {
try {
logger.info("开起退款。。。。。。");
//获取退款订单号
String backOrder = orderNumber;
//注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//指向你的证书的绝对路径,带着证书去访问
// FileInputStream instream = new FileInputStream("D:\\cet\\apiclient_cert.p12");//P12文件目录
FileInputStream instream = new FileInputStream("D:\\ysyg\\certificate\\apiclient_cert.p12");//P12文件目录
try {
//下载证书时的密码、默认密码是你的MCHID mch_id
keyStore.load(instream, wxmch_id.toCharArray());//这里写密码
} finally {
instream.close();
}
//下载证书时的密码、默认密码是你的MCHID mch_id
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, wxmch_id.toCharArray())//这里也是写密码的
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( // new String[]{"TLSv1"},
sslcontext,
null,
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
// String appid = "wx4998abf291a3eba8";//公众账号id
String nonce_str = generateNonceStr();//随机字符串
String total_fee = new DecimalFormat("0").format(totalFee * 100);//订单金额
String refund_fee = new DecimalFormat("0").format(refundFee * 100);//退款金额
Map<String, String> paramsMap = new HashMap<String, String>();//String转Map
paramsMap.put("appid", user_appid);//公众号账号
paramsMap.put("mch_id", wxmch_id);//商户号
paramsMap.put("nonce_str", nonce_str);//随机字符串
// paramsMap.put("out_trade_no", out_trade_no);//商户订单号
paramsMap.put("out_refund_no", backOrder);//商户退款单号
paramsMap.put("total_fee", total_fee);//订单金额
paramsMap.put("refund_fee", refund_fee);//退款金额
paramsMap.put("transaction_id", transaction_id);//微信单号
paramsMap.put("notify_url", backResultURL);//退款回调
//加入数据库
logger.info("退款数据包====" + paramsMap);
String mchKey = wxmchKey; //微信平台密钥
//签名:传数据的Map结构,传微信平台的密钥,加密方式
String sign = generateSignature(paramsMap, mchKey, WXPayConstants.SignType.MD5);
paramsMap.put("sign", sign);//签名
String xml = mapToXml(paramsMap);//将所有参数(map)转xml格式
String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";//请求网址
System.out.println("xml===" + xml);
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(xml, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
System.out.println(jsonStr);
logger.info("退款参数==========" + jsonStr);
Map<String, String> map = null;
map = WXPayUtil.xmlToMap(jsonStr);
reObj.put("return", map.get("return_code"));
reObj.put("returnMsg", map.get("return_msg"));
// //退款请求后的操作(参数顺序与退款方法参数顺序一致)
// wxRefundToDoImpl.refundOverToDo(out_trade_no,out_refund_no,transaction_id,totalFee,refundFee);
} finally {
response.close();
}
} finally {
httpclient.close();
}
} catch (Exception e) {
e.printStackTrace();
logger.error("微信退款异常:" + e);
}
// }).start();
//返回结果
return reObj;
}
2.退款成功
/**
* 微信退款完成通知处理
*
* @return {out_trade_no,returnMsg}成功返回订单对象,失败返回带有订单编号及失败信息的对象
* @throws Exception
*/
//微信退款回调--退款回调
@RequestMapping("/WXsuccessBack")
@ApiOperation(value = "微信退款完成通知处理")
public String WXbackOk(HttpServletResponse httpServletResponse) throws IOException {
String rr = "<xml>" +
" <return_code><![CDATA[SUCCESS]]></return_code>" +
" <return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
try {
RequestWrapper requestWrapper = new RequestWrapper(request);
String requestBody = requestWrapper.getRequestBody(request);
JSONObject bodyJson = XmlTool.documentToJSONObject(requestBody);
//对微信api密钥加密
String key = Base64Utiltool.MD5(wxmchKey);
String num1 = decryptData(bodyJson.getString("req_info"), key);
logger.info("AES-256-ECB解密=====" + num1);
Map<String, String> xmlMap = Base64Utiltool.readStringXmlOut(num1);
logger.info("====**********************************************===="+xmlMap);
String out_refund_no=xmlMap.get("out_trade_no");
//退款成功之后的操作
} catch (Exception e) {
e.printStackTrace();
logger.error("支付回调异常===" + e);
}
return rr;
}
/** AES解密 */
/**
* 传需要解密的微信回调信息,和加密过的密钥
* @param base64Data
* @param password
* @return
* @throws Exception
*/
public static String decryptData(String base64Data,String password) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
SecretKeySpec key = new SecretKeySpec(password.getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decode = Base64Utiltool.decode(base64Data);
byte[] doFinal = cipher.doFinal(decode);
return new String(doFinal,"utf-8");
}
package com.ddm.system.tools;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Base64Utiltool {
/**
* 解码
* @param encodedText
* @return
*/
public static byte[] decode(String encodedText){
final Base64.Decoder decoder = Base64.getMimeDecoder();
return decoder.decode(encodedText);
}
/**
* 编码
* @param data
* @return
*/
public static String encode(byte[] data){
final Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(data);
}
/** 普通MD5 */
public static String MD5(String input) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
return "check jdk";
} catch (Exception e) {
e.printStackTrace();
return "";
}
char[] charArray = input.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++) {
byteArray[i] = (byte) charArray[i];
}
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
/**将16进制转换为二进制
* @param hexStr
* @return
*/
public static byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length()/2];
for (int i = 0;i< hexStr.length()/2; i++) {
int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
/**
* 将二进制转换成16进制
* @method parseByte2HexStr
* @param buf
* @return
* @throws
* @since v1.0
*/
public static String parseByte2HexStr(byte buf[]){
StringBuffer sb = new StringBuffer();
for(int i = 0; i < buf.length; i++){
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* InputStream流转换成String字符串
* @param inStream InputStream流
* @param encoding 编码格式
* @return String字符串
*/
public static String inputStreamToString(InputStream inStream, String encoding){
String result = null;
try {
if(inStream != null){
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] tempBytes = new byte[1024];
int count = -1;
while((count = inStream.read(tempBytes, 0, 1024)) != -1){
outStream.write(tempBytes, 0, count);
}
tempBytes = null;
outStream.flush();
result = new String(outStream.toByteArray(), encoding);
}
} catch (Exception e) {
result = null;
}
return result;
}
/**
* xml转换成map
* @param xml
* @return
*/
public static Map<String, String> readStringXmlOut(String xml) {
Map<String, String> map = new HashMap<String, String>();
Document doc = null;
try {
doc = DocumentHelper.parseText(xml); // 将字符串转为XML
Element rootElt = doc.getRootElement(); // 获取根节点
List<Element> list = rootElt.elements();// 获取根节点下所有节点
for (Element element : list) { // 遍历节点
map.put(element.getName(), element.getText()); // 节点的name为map的key,text为map的value
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
package com.ddm.baseService.tools;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.log4j.Logger;
import org.dom4j.*;
import java.util.List;
/**
* xml工具类
*
* @author sleep
* @date 2016-09-13
*/
public class XmlTool {
private static Logger logger = Logger.getLogger(XmlTool.class);
/**
* String 转 org.dom4j.Document
*
* @param xml
* @return
* @throws DocumentException
*/
public static Document strToDocument(String xml) throws DocumentException {
return DocumentHelper.parseText(xml);
}
/**
* org.dom4j.Document 转 com.alibaba.fastjson.JSONObject
*
* @param xml
* @return
* @throws DocumentException
*/
public static JSONObject documentToJSONObject(String xml) throws DocumentException {
return elementToJSONObject(strToDocument(xml).getRootElement());
}
/**
* org.dom4j.Element 转 com.alibaba.fastjson.JSONObject
*
* @param node
* @return
*/
public static JSONObject elementToJSONObject(Element node) {
JSONObject result = new JSONObject();
// 当前节点的名称、文本内容和属性
List<Attribute> listAttr = node.attributes();// 当前节点的所有属性的list
for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
result.put(attr.getName(), attr.getValue());
}
// 递归遍历当前节点所有的子节点
List<Element> listElement = node.elements();// 所有一级子节点的list
if (!listElement.isEmpty()) {
for (Element e : listElement) {// 遍历所有一级子节点
if (e.attributes().isEmpty() && e.elements().isEmpty()) // 判断一级节点是否有属性和子节点
result.put(e.getName(), e.getTextTrim());// 沒有则将当前节点作为上级节点的属性对待
else {
if (!result.containsKey(e.getName())) // 判断父节点是否存在该一级节点名称的属性
result.put(e.getName(), new JSONArray());// 没有则创建
((JSONArray) result.get(e.getName())).add(elementToJSONObject(e));// 将该一级节点放入该节点名称的属性对应的值中
}
}
}
return result;
}
public static void main(String[] args) throws Exception {
logger.info(documentToJSONObject("<xml><ToUserName><![CDATA[gh_b426595b0475]]></ToUserName>" +
"<FromUserName><![CDATA[o9Mm208CGUJh8CpGnFUfoEck4XoQ]]></FromUserName>" +
"<CreateTime>1542266839</CreateTime>" +
"<MsgType><![CDATA[text]]></MsgType>" +
"<Content><![CDATA[。。。]]></Content>" +
"<MsgId>6623985635807987968</MsgId>" +
"</xml>"));
}
}