微信支付NATIVE版V2旧版
工具类
package com.xihui.nyy_cloud.utils;
import com.xihui.nyy_cloud.constant.ConstWeiChat;
import com.xihui.nyy_cloud.constant.WXPayConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.*;
import java.security.*;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 统一下单 v2版 Native
* @param mchId 商户号
* @param body 商品描述
* @param attach 附加数据(可有可无)
* @param orderId 商家订单号
* @param totalFee 总金额
* @param ip 用户请求ip地址
* @param notifyUrl 支付结果回调地址
* @return 返回封装好后的数据,包括签名
* @throws Exception
*/
public static Map<String, String> genParamToMap(String mchId,String body,String attach,String orderId,String totalFee,String ip,String notifyUrl) throws Exception {
Map<String, String> paraMap = new HashMap<>(16);
paraMap.put("appid", ConstWeiChat.APPID); //appid
//附加数据,在查询API和支付通知中原样返回
paraMap.put("attach", attach);
paraMap.put("body", "NYY-" + body);//商品描述
paraMap.put("mch_id", mchId);
paraMap.put("nonce_str", WXPayUtil.generateNonceStr()); //随机数
//此路径是微信服务器调用支付结果通知路径
paraMap.put("notify_url",notifyUrl);
//订单号
paraMap.put("out_trade_no", orderId);
paraMap.put("spbill_create_ip", ip);//用户请求的ip
paraMap.put("total_fee", totalFee);
paraMap.put("trade_type", "NATIVE "); //Native的交易类型
String sign = WXPayUtil.generateSignature(paraMap, ConfigUtil.API_KEY);
paraMap.put("sign", sign);
return paraMap;
}
/**
* 退款
* @param mchId
* @param orderId
* @param totalFee
* @param notifyUrl
* @return
* @throws Exception
*/
public static Map<String, String> genParamToMapRefund(String mchId,String orderId,String outRefundNo,String totalFee,String refundFee,String notifyUrl) throws Exception {
Map<String, String> paraMap = new HashMap<>(16);
paraMap.put("appid", ConstWeiChat.APPID);
paraMap.put("mch_id", mchId);
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
//此路径是微信服务器调用支付结果通知路径
paraMap.put("notify_url",notifyUrl);
//订单号
paraMap.put("out_trade_no", orderId);
paraMap.put("out_refund_no",outRefundNo);
paraMap.put("total_fee", totalFee);
paraMap.put("refund_fee", refundFee);
paraMap.put("refund_fee_type", "RMB");
String sign = WXPayUtil.generateSignature(paraMap, ConfigUtil.API_KEY);
paraMap.put("sign", sign);
return paraMap;
}
/**
* 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"));
org.w3c.dom.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) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.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();
org.w3c.dom.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, "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, WXPayConstants.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, WXPayConstants.SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.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(WXPayConstants.FIELD_SIGN)) {
return false;
}
String sign = data.get(WXPayConstants.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, WXPayConstants.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, WXPayConstants.SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
return false;
}
String sign = data.get(WXPayConstants.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, WXPayConstants.SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.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(WXPayConstants.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 (WXPayConstants.SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
} else if (WXPayConstants.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 Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 获取当前时间戳,单位秒
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 获取当前时间戳,单位毫秒
*
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* 读取流中字符串
*
* @param in 输入流
* @param type 编码
* @return
*/
public static String inputStream2String(InputStream in, String type) {
InputStreamReader reader = null;
try {
reader = new InputStreamReader(in, type);
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
BufferedReader br = new BufferedReader(reader);
StringBuilder sb = new StringBuilder();
String line = "";
try {
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
/**
* 加载证书
* @param path
* @throws IOException
*/
private void initCert(String path) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {
}
}
package com.xihui.nyy_cloud.utils;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* 2018/7/3
*/
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();
}
}
public class ConfigUtil {
/*
* 服务号相关信息
*/
public final static String APPID = "";//服务号的应用号
public final static String MCH_ID = "";//商户号
public final static String API_KEY = "";//API密钥
public final static String SIGN_TYPE = "MD5";//签名加密方式
/**
* 证书地址
*/
public static String certLocalPath = "";
/**
* 统一下单-h5
*/
public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 查询订单
*/
public final static String ORDER_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
/**
* 统一下单Body字段App前缀
*/
public final static String BODY_NAME = "";
}
package com.xihui.nyy_cloud.utils;
import javax.servlet.http.HttpServletRequest;
public class CommonUtil {
public static String toIpAddr(HttpServletRequest request) {
String ip = null;
//X-Forwarded-For:Squid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//Proxy-Client-IP:apache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//WL-Proxy-Client-IP:weblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//HTTP_CLIENT_IP:有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//X-Real-IP:nginx服务代理
ipAddresses = request.getHeader("X-Real-IP");
}
//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
if (ipAddresses != null && ipAddresses.length() != 0) {
ip = ipAddresses.split(",")[0];
}
//还是不能获取到,最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}
}
package com.xihui.nyy_cloud.utils;
import com.xihui.nyy_cloud.exception.CustomException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
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.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
/**
* @author bing.li
* @date 2020/9/12 9:23
*/
public class HttpCilentUtil {
/**
* post发送请求,xml
*
* @param url
* @param data
* @return
*/
public static String postForobject(String url, String data) {
HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost(url);
try {
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1000).setConnectTimeout(3000).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return EntityUtils.toString(httpEntity, "UTF-8");
} catch (ClientProtocolException e) {
e.printStackTrace();
throw new CustomException("网络异常,请检查重试");
} catch (IOException e) {
e.printStackTrace();
throw new CustomException("网络异常,请稍后重试");
}catch (Exception e){
e.printStackTrace();
throw new CustomException("网络异常,请稍后重试");
} finally {
}
}
/**
* 加载证书发送post请求,退款
*/
public static String postForobject(String url, String data,String path) {
HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost(url);
try {
//拼接证书的路径
path = path + ConfigUtil.certLocalPath;
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//加载本地的证书进行https加密传输
FileInputStream instream = new FileInputStream(new File(path));
try {
keyStore.load(instream, ConfigUtil.MCH_ID.toCharArray()); //加载证书密码,默认为商户ID
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, ConfigUtil.MCH_ID.toCharArray()) //加载证书密码,默认为商户ID
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1000).setConnectTimeout(3000).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return EntityUtils.toString(httpEntity, "UTF-8");
} catch (ClientProtocolException e) {
e.printStackTrace();
throw new CustomException("网络异常,请检查重试");
} catch (IOException e) {
e.printStackTrace();
throw new CustomException("网络异常,请稍后重试");
}catch (Exception e){
e.printStackTrace();
throw new CustomException("网络异常,请稍后重试");
} finally {
}
}
}
常量
/**
* 常量
*/
public class WXPayConstants {
public enum SignType {
MD5, HMACSHA256
}
public static final String FAIL = "FAIL";
public static final String SUCCESS = "SUCCESS";
public static final String HMACSHA256 = "HMAC-SHA256";
public static final String MD5 = "MD5";
public static final String FIELD_SIGN = "sign";
public static final String FIELD_SIGN_TYPE = "sign_type";
}
微信支付示例
支付代码
public Map<String, String> wxPrePay(HttpServletRequest request, HttpServletResponse response, Integer price, String orderNum) {
//创建接收返回值参数和发送统一下单参数的map
Map<String, String> resultMap = new HashMap<>();
Map<String, String> param = new HashMap<>();
//判断金额是否小于0
if (price <= 0) {
resultMap.put("message", "付款金额小于0");
resultMap.put("code", "500");
return resultMap;
}
//微信支付结果回调地址
String url = ConstWeiChat.HUI_DIAO_URL + ConstUrl.PRE_ORDER_PATH + ConstWeiChat.WEI_XIN_HUI_DIAO_URL;
//填充参数并计算签名
try {
param = WXPayUtil.genParamToMap(ConfigUtil.MCH_ID, "body参数", "", orderNum, price.toString(), CommonUtil.toIpAddr(request), url);
} catch (Exception e) {
e.printStackTrace();
}
//请求参数由map转换为XML格式字符串
String paramXML = null;
try {
paramXML = WXPayUtil.mapToXml(param);
} catch (Exception e) {
e.printStackTrace();
}
//调用统一下单接口,并接收返回数据
String result = HttpCilentUtil.postForobject(ConfigUtil.UNIFIED_ORDER_URL, paramXML);
System.err.println("返回报文:" + result);
//返回数据转换为map类型
try {
Map<String, String> map = WXPayUtil.xmlToMap(result);
//判断返回的结果数据是否成功
if (!"SUCCESS".equals(map.get("return_code"))) {
resultMap.put("code", "501");
resultMap.put("message", map.get("return_msg"));
return resultMap;
}
//当return_code的返回值为SUCCESS时才会有这个字段数据
if (!"SUCCESS".equals(map.get("result_code"))) {
resultMap.put("code", "502");
resultMap.put("message", map.get("err_code_des"));
return resultMap;
}
//当return_code和result_code都为SUCCESS时获取返回的响应数据
resultMap.put("code", "200");
resultMap.put("message", map.get("code_url"));
resultMap.put("orderNum", orderNum);
System.err.println("Native支付返回的调起网页地址:" + map.get("code_url"));
} catch (Exception e) {
e.printStackTrace();
}
return resultMap;
}
微信回调支付状态
public void wxNotify(HttpServletRequest request, HttpServletResponse response) {
try {
//读取参数
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream(); //获取请求的输入流
String s;
//读取输入流并放到缓冲流中
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) { //读取一行信息并不等于null时放入到sb中
sb.append(s);
}
in.close(); //关闭缓冲流
inputStream.close(); //关闭输入流
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
m = XMLUtil.doXMLParse(sb.toString());
for (Object keyValue : m.keySet()) {
System.out.println(keyValue + "=" + m.get(keyValue));
}
//过滤空 设置 过滤空值是为了校验签名,签名计算空值是不参与生成签名的
Map<String, String> packageParams = new HashMap<>();
Iterator it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = m.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
//判断签名是否正确
String resXml = "";
try {
if (WXPayUtil.isSignatureValid(packageParams, ConfigUtil.API_KEY)) {
if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
// 这里是支付成功
//执行自己的业务逻辑\
final String out_trade_no = (String) packageParams.get("out_trade_no"); //商户订单号
//查询订单信息
TOrder tOrder = tOrderMapper.selectOne(new QueryWrapper<TOrder>().eq("order_num", out_trade_no));
if (tOrder == null) {
log.info("支付失败,错误信息:" + "订单编号在数据库中不存在");
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[参数错误]]></return_msg>" + "</xml> ";
}
Integer totalFee = tOrder.getFkPrice();
//根据订单号主动请求微信支付查询订单接口
Map<String, String> map = orderQuery(out_trade_no);
//return_code和result_code都为SUCCESS标识时才算成功
if ("SUCCESS".equals(map.get("return_code"))) {
String outTradeNo = map.get("out_trade_no");
String total_fee = map.get("total_fee");
String mchId = map.get("mch_id");
String attachs = map.get("attach");
if ("SUCCESS".equals(map.get("result_code"))) {
//判断商户号是否一致、统一下单编码是否一致、付款金额是否一致
if (!ConfigUtil.MCH_ID.equals(mchId) || !out_trade_no.equals(outTradeNo) || !totalFee.toString().equals(total_fee)) {
log.info("支付失败,错误信息:" + "商家号或统一下单编号或付款金额不一致");
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[参数错误]]></return_msg>" + "</xml> ";
} else {
//判断当前订单状态是否为代付款状态,如果为代付款状态,则修改状态
if (0 == tOrder.getStatus()) {
//修改订单状态
tOrderMapper.changeOrderStatus(outTradeNo, 1, new Date());
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
log.info("订单已处理");
}
}
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[通知签名验证失败]]></return_msg>" + "</xml> ";
log.info("查询订单失败,{}", map.get("return_msg"));
}
} else {
log.info("支付失败,错误信息:" + packageParams.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[通知签名验证失败]]></return_msg>" + "</xml> ";
log.info("通知签名验证失败");
}
} catch (Exception e) {
log.error("微信支付回调异常{}", e.getMessage());
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[未知异常]]></return_msg>" + "</xml> ";
}
//------------------------------
//处理业务完毕
//------------------------------
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} catch (Exception e) {
log.error("微信支付回调异常{}", e.getMessage());
throw new CustomException("解析微信回调报文异常");
}
}
主动请求查询微信支付状态
public void weixinUpdateOrderStatus(String orderNum) {
//根据订单号查询订单是否存在
TOrder tOrder = tOrderMapper.selectOne(new QueryWrapper<TOrder>().eq("order_num", orderNum));
if (tOrder == null) {
throw new CustomException("订单不存在,请刷新页面");
}
Integer totalFee = tOrder.getFkPrice();
//根据订单号主动请求微信支付查询订单接口
Map<String, String> map = orderQuery(orderNum);
if ("SUCCESS".equals(map.get("return_code"))) {
String outTradeNo = map.get("out_trade_no");
String total_fee = map.get("total_fee");
String mchId = map.get("mch_id");
if ("SUCCESS".equals(map.get("result_code"))) {
//判断商户号是否一致、统一下单编码是否一致、付款金额是否一致
if (!ConfigUtil.MCH_ID.equals(mchId) || !orderNum.equals(outTradeNo) || !totalFee.toString().equals(total_fee)) {
throw new CustomException("商户号、下单编码、付款金额不一致,不做修改");
} else {
//判断当前订单状态是否为代付款状态,如果为代付款状态,则修改状态
if ("SUCCESS".equals(map.get("trade_state"))) {
if (0 == tOrder.getStatus()) {
//修改订单状态
tOrderMapper.changeOrderStatus(outTradeNo, 1, new Date());
}
}
}
}
} else {
throw new CustomException("查询微信订单失败{ " + map.get("return_msg") + "}");
}
}
退款实例
退款需要证书
退款代码
public Map<String, String> weiXinRefund(HttpServletRequest request, HttpServletResponse response, Long id) {
Map<String, String> resultMap = new HashMap<>();
Map<String, String> parameters = new HashMap<>();
//根据订单id查询订单信息
TOrderLord tOrderLord = tOrderLordMapper.selectById(id);
//根据当时统一下单的微信编码,获取所有下单金额
List<TOrderLord> tOrderLords = tOrderLordMapper.selectList(new QueryWrapper<TOrderLord>().eq("weixin_code", tOrderLord.getWeixinCode()));
Integer totalMoney = 0;
for (TOrderLord orderLord : tOrderLords) {
totalMoney += orderLord.getFkPrice();
}
//设置回调地址
// String url = request.getRequestURI().toString();
// String domain = url.substring(0, url.length() - 13);
//生产环境
String notify_url = ConstWeiChat.HUI_DIAO_URL + ConstUrl.PRE_WEI_XIN_PATH + ConstWeiChat.WEI_XIN_REFUND_URL; //回调地址
//生成退款订单号
String outRefundNo = ConstOrder.ORDER_REFUND_PRE + DateUtil.getNowTimeyyyyMMddHHmmssSSS_CN();
try {
parameters = WXPayUtil.genParamToMapRefund(ConfigUtil.MCH_ID, tOrderLord.getWeixinCode(), outRefundNo, totalMoney.toString(), tOrderLord.getFkPrice().toString(), notify_url);
} catch (Exception e) {
e.printStackTrace();
}
String requestXML = null;
try {
requestXML = WXPayUtil.mapToXml(parameters);
} catch (Exception e) {
log.error("退款Map参数,转换为XML格式异常{}", e.getMessage());
e.printStackTrace();
}
System.out.println(requestXML);
String result = HttpCilentUtil.postForobject(ConfigUtil.REFUND_URL, requestXML,"");
System.out.println("发送退款,返回结果数据:"+result);
try {
//解析退款返回信息
resultMap = WXPayUtil.xmlToMap(result);
if (resultMap.get("return_code").equals("FAIL")) {
throw new CustomException(resultMap.get("return_msg"));
}
if (resultMap.get("result_code").equals("FAIL")) {
throw new CustomException("请求成功,但提交退款业务失败,失败原因:" + resultMap.get("err_code_des"));
}
//退款申请发送成功后,修改订单退款状态为退款中状态
tOrderService.orderRefund(id, outRefundNo);
} catch (Exception e) {
log.error("异常:{}" + e.getMessage());
throw new CustomException("异常:{"+e.getMessage()+"}");
}
return resultMap;
}
退款回调
public void weiXinAutoRefundMessage(HttpServletRequest request, HttpServletResponse response) {
try {
//读取参数
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
m = XMLUtil.doXMLParse(sb.toString());
for (Object keyValue : m.keySet()) {
System.out.println(keyValue + "=" + m.get(keyValue));
}
//过滤空 设置
Map<String, String> packageParams = new HashMap<>();
Iterator it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = m.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
String resXml = "";
try {
if (WXPayUtil.isSignatureValid(packageParams,ConfigUtil.API_KEY)) {
//退款成功通知
if ("SUCCESS".equals(packageParams.get("return_code"))) {
//用Base64编码解码ciphertext字段
byte[] decode = Base64.getDecoder().decode(packageParams.get("req_info"));
String str = new String(decode, "UTF-8");
String key = Md5Util.MD532(ConfigUtil.API_KEY);
//用key对res进行解密
String rest = AESUtil.decryptData(str, key);
//解密后的xml格式转换为map格式
Map<String, String> resMap = WXPayUtil.xmlToMap(rest);
//根据退款编码查询该订单退款金额与当前通知退款金额是否相符
final TOrderLord refundOrderInfo = tOrderLordMapper.getRefundCodeOrderInfo(resMap.get("out_refund_no"));
//判断退款金额与订单付款金额是否相等
if(!refundOrderInfo.getFkPrice().equals(resMap.get("settlement_refund_fee"))){
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[退款金额与实际退款金额不符]]></return_msg>" + "</xml> ";
log.info("退款金额与实际退款金额不符");
}
//根据退款编码查询退款订单信息
Map<String, String> map = getWeixinRefundInfo(resMap.get("out_refund_no"));
//return_code和result_code都为SUCCESS标识时才算成功
if ("SUCCESS".equals(map.get("return_code"))) {
String outRefundNo = map.get("out_refund_no_0");
String refundFee = map.get("refund_fee_0");
String mchId = map.get("mch_id");
if ("SUCCESS".equals(map.get("result_code"))) {
//判断商户号是否一致、退款编码是否一致、统一下单编码是否一致、付款金额是否一致
if (!ConfigUtil.MCH_ID.equals(mchId) || !resMap.get("out_trade_no").equals(refundOrderInfo.getWeixinCode()) || !refundOrderInfo.getWeixinRefund().equals(outRefundNo) || !refundOrderInfo.getFkPrice().toString().equals(refundFee)) {
log.info("退款失败,错误信息:" + "参数错误");
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[参数错误]]></return_msg>" + "</xml> ";
} else {
if(!"PROCESSING".equals(map.get("refund_status_0"))) { //退款状态为正在处理
//修改之前先查看当前订单是否已退款成功或失败
if (refundOrderInfo.getRefunded() == 1) {
Integer refund = 0;
if ("SUCCESS".equals(map.get("refund_status_0"))) { //退款状态为成功
refund = 2;
}
if ("REFUNDCLOSE".equals(map.get("refund_status_0")) || "CHANGE".equals(map.get("refund_status_0"))) { //退款状态为关闭或异常都作为失败处理
refund = 3;
}
//修改订单状态
tOrderLordMapper.changeRefundOrder(outRefundNo, refund);
if(refund == 2){
//生成退款记录
final TOrderLord tOrderLord = tOrderLordMapper.selectOne(new QueryWrapper<TOrderLord>()
.eq("weixin_refund", resMap.get("out_refund_no")).last("limit 1"));
final List<TOrder> orderList = tOrderMapper.selectList(new QueryWrapper<TOrder>().eq("order_lord_id", tOrderLord.getId()));
final TUser tUser = tUserMapper.selectById(tOrderLord.getUserId());
orderList.forEach(order->{
//生成商家收钱记录表信息
TBussinessMoneyRecords tBussinessMoneyRecords = new TBussinessMoneyRecords();
tBussinessMoneyRecords.setShopId(tOrderLord.getShopId())
.setUserImg(tUser.getHeadPhoto())
.setUserName(tUser.getUserName())
.setGoodsImg(order.getHeadImg())
.setGoodsName(order.getGoodsName())
.setGoodsPrice(order.getPrice())
.setGoodsNum(order.getNum())
.setTotalPrice(order.getTotalMoney())
.setHasPlus(false);
if(Const.CONST_STATUS_ZERO == tBussinessMoneyRecordsMapper.insert(tBussinessMoneyRecords)){
log.error("WeixinServiceImpl,生成商家收钱记录错误,记录信息为:"+tBussinessMoneyRecords.toString());
throw new CustomException("生成退款收钱记录错误,请刷新重试");
}
});
TBussinessMoneyRecords tBussinessMoneyRecords = new TBussinessMoneyRecords();
tBussinessMoneyRecords.setShopId(tOrderLord.getShopId())
.setUserImg(tUser.getHeadPhoto())
.setUserName(tUser.getUserName())
.setGoodsImg("")
.setGoodsName("运费")
.setGoodsPrice(tOrderLord.getFreight())
.setGoodsNum(1)
.setTotalPrice(tOrderLord.getFreight())
.setHasPlus(false);
if(Const.CONST_STATUS_ZERO == tBussinessMoneyRecordsMapper.insert(tBussinessMoneyRecords)){
log.error("WeixinServiceImpl,生成商家运费收钱记录错误,记录信息为:"+tBussinessMoneyRecords.toString());
throw new CustomException("生成退款收钱记录错误,请刷新重试");
}
}
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
log.info("退款订单已处理");
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[退款状态错误]]></return_msg>" + "</xml> ";
}
}
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[通知签名验证失败]]></return_msg>" + "</xml> ";
log.info("查询退款订单失败,{}", map.get("return_msg"));
}
} else {
log.info("退款失败,错误信息:" + packageParams.get("return_msg"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[通知签名验证失败]]></return_msg>" + "</xml> ";
log.info("通知签名验证失败");
}
} catch (Exception e) {
log.error("微信退款回调异常{}", e.getMessage());
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[未知异常]]></return_msg>" + "</xml> ";
}
//------------------------------
//处理业务完毕
//------------------------------
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} catch (Exception e){
log.error("微信退款回调异常{}", e.getMessage());
throw new CustomException("解析微信回调报文异常");
}
}
主动查询退款状态
public void changeWeixinRefundOrder(Long id) {
//根据订单id查询订单信息
TOrderLord orderLord = tOrderLordMapper.selectById(id);
if(orderLord.getRefunded() == 1){ //退款状态为1时,表示还在退款中,查看微信订单退款状态
//根据退款编码查询订单退款状态
Map<String, String> map = getWeixinRefundInfo(orderLord.getWeixinRefund());
System.out.println(map);
if ("SUCCESS".equals(map.get("return_code"))) {
String outRefundNo = map.get("out_refund_no_0");
String refundFee = map.get("refund_fee_0");
String mchId = map.get("mch_id");
if ("SUCCESS".equals(map.get("result_code"))) {
//判断商户号是否一致、退款编码是否一致、统一下单编码是否一致、付款金额是否一致
System.err.println("ConfigUtil.MCH_ID" + ConfigUtil.MCH_ID);
System.err.println("mchId" + mchId);
System.err.println("map.get(\"out_trade_no\")" + map.get("out_trade_no"));
System.err.println("orderLord.getWeixinCode()" + orderLord.getWeixinCode());
System.err.println("map.get(\"out_refund_no_0\")" + map.get("out_refund_no_0"));
System.err.println("outRefundNo" + outRefundNo);
System.err.println("orderLord.getFkPrice()" + orderLord.getFkPrice());
System.err.println("refundFee" + refundFee);
if (!ConfigUtil.MCH_ID.equals(mchId) || !map.get("out_trade_no").equals(orderLord.getWeixinCode()) || !orderLord.getWeixinRefund().equals(outRefundNo) || !orderLord.getFkPrice().toString().equals(refundFee)) {
log.info("失败,错误信息:" + "参数错误");
} else {
if(!"PROCESSING".equals(map.get("refund_status_0"))) { //退款状态为正在处理
//修改之前先查看当前订单是否已退款成功或失败
if (orderLord.getRefunded() == 1) {
Integer refund = 1;
if ("SUCCESS".equals(map.get("refund_status_0"))) { //退款状态为成功
refund = 2;
}
if ("REFUNDCLOSE".equals(map.get("refund_status_0")) || "CHANGE".equals(map.get("refund_status_0"))) { //退款状态为关闭或异常都作为失败处理
refund = 3;
}
//修改订单状态
tOrderLordMapper.changeRefundOrder(outRefundNo, refund);
}
}
}
}
} else {
log.info("查询退款订单失败,{}", map.get("return_msg"));
}
}
}