微信支付
备注:本次支付接入的是微信支付v2.0版本,其中所有的请求格式均为XML。
如有需求可直接参阅官方文档 https://pay.weixin.qq.com/wiki/doc/api/index.html
官网支付demo地址(v2.0): https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
微信支付V3.0版本是基于JSON格式的API。
个人支付案例git地址[微信支付/支付宝支付/华为支付/苹果支付/小米支付]:https://gitee.com/wazk2008/demo-pay
1.下单支付(跳转微信进行支付)
微信接口名称: 统一下单
微信接口地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
个人支付代码demo
@RestController
@RequestMapping(value = "/pay")
@Slf4j
public class PayController {
@Autowired
private WechatService wechatService;
@PostMapping(value = "/wechat")
public Result<PayWechatResponse> payWechat (@RequestBody PayWechatRequest payWechatRequest) {
try {
log.info("微信支付接口开始执行,请求参数:{}", JSON.toJSONString(payWechatRequest));
PayWechatResponse payWechatResponse = wechatService.pay(payWechatRequest);
log.info("微信支付接口执行成功,请求参数:{},响应参数:{}", JSON.toJSONString(payWechatRequest), JSON.toJSON(payWechatResponse));
return Result.getSuccess(payWechatResponse);
} catch (ResponseException e) {
log.error("微信支付接口执行失败1,请求参数:{},异常原因:{}", JSON.toJSONString(payWechatRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(e);
} catch (Exception e) {
log.error("微信支付接口执行失败2,请求参数:{},异常原因:{}", JSON.toJSONString(payWechatRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(ResultCode.INTERNAL_SERVER_ERROR);
}
}
}
@Service
@Slf4j
public class WechatService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private Map<String, WechatAppConfig> wechatApps;
@Autowired
private HttpXmlRequest httpXmlRequest;
public PayWechatResponse pay (PayWechatRequest payWechatRequest) throws Exception {
// 这里可以写各种校验
// 校验appid是否合法
String appid = payWechatRequest.getAppid();
WechatAppConfig wechatApp = wechatApps.get(appid);
if (Objects.isNull(wechatApp)) {
throw new ResponseException(ResultCode.WECHAT_PAY_APPID_ERROR);
}
// 校验支付方式tradeType是否合法
String tradeType = payWechatRequest.getTradeType();
boolean isContainTradeType = wechatApp.getTradeType().contains(tradeType);
if (!isContainTradeType) {
throw new ResponseException(ResultCode.WECHAT_PAY_TRADE_TYPE_NAME_ERROR);
}
// todo 校验该比订单的支付金额是否合法
// todo 校验该笔订单的状态是否为待支付
// 获取请求微信的xml参数
String xml = getPayValidRequestXml(payWechatRequest);
String responseXml = httpXmlRequest.postXml(wechatConfig.getUnifiedOrderUrl(), xml);
Map<String, String> map = WXPayUtil.xmlToMap(responseXml);
// 返回状态码==SUCCESS && 业务结果==SUCCESS 表示退款申请成功
if (WechatConstant.SUCCESS.equals(map.get(WechatConstant.RETURN_CODE)) && WechatConstant.SUCCESS.equals(map.get(WechatConstant.RESULT_CODE))) {
// todo 这里可以保存用户微信支付的唤醒记录
WechatTradeTypeEnum wechatTradeTypeEnum = WechatTradeTypeEnum.valueOf(payWechatRequest.getTradeType());
return generatePayWechatResponse(wechatTradeTypeEnum, appid, map);
}
throw new ResponseException(ResultCode.WECHAT_PAY_ERROR);
}
// 获取请求微信的xml参数
private String getPayValidRequestXml (PayWechatRequest payWechatRequest) throws Exception {
// 获取请求微信的xml对应的map
Map<String, String> payXmlMap = getPayXmlMap(payWechatRequest);
// 校验生成的签名
boolean signatureValid = WXPayUtil.isSignatureValid(payXmlMap, wechatApps.get(payWechatRequest.getAppid()).getAppSecret());
if (!signatureValid) {
throw new ResponseException(ResultCode.WECHAT_PAY_SIGNATURE_VALID_ERROR);
}
//
return WXPayUtil.mapToXml(payXmlMap);
}
private Map<String, String> getPayXmlMap (PayWechatRequest payWechatRequest) {
Map<String, String> reqData = new HashMap<>();
String appid = payWechatRequest.getAppid();
WechatAppConfig wechatApp = wechatApps.get(appid);
reqData.put(WechatConstant.APPID, appid);
reqData.put(WechatConstant.MCH_ID, wechatApp.getMchId());
reqData.put(WechatConstant.NONCE_STR , WXPayUtil.generateNonceStr());
reqData.put(WechatConstant.BODY , payWechatRequest.getBody());
reqData.put(WechatConstant.OUT_TRADE_NO , payWechatRequest.getOrderNo());
reqData.put(WechatConstant.TOTAL_FEE , payWechatRequest.getFee());
reqData.put(WechatConstant.SPBILL_CREATE_IP , payWechatRequest.getSpbillCreateIp());
reqData.put(WechatConstant.NOTIFY_URL , wechatConfig.getUnifiedOrderCallbackUrl());
reqData.put(WechatConstant.TRADE_TYPE , payWechatRequest.getTradeType());
if (WechatTradeTypeEnum.JSAPI.name().equals(payWechatRequest.getTradeType())) {
reqData.put(WechatConstant.OPENID , payWechatRequest.getOpenid());
}
if (WechatConstant.MD5.equals(wechatApp.getSignType())) {
reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.MD5);
}
else if (WechatConstant.HMACSHA256.equals(wechatApp.getSignType())) {
reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.HMACSHA256);
} else {
throw new ResponseException(ResultCode.WECHAT_CONFIG_SIGN_TYPE_ERROR);
}
try {
reqData.put(WechatConstant.SIGN, WXPayUtil.generateSignature(reqData, wechatApp.getAppSecret(), WechatSignTypeEnum.valueOf(wechatApp.getSignType())));
} catch (Exception e) {
log.error("微信支付生成签名错误");
e.printStackTrace();
throw new ResponseException(ResultCode.WECHAT_PAY_GENERATE_SIGN_ERROR);
}
return reqData;
}
private PayWechatResponse generatePayWechatResponse(WechatTradeTypeEnum wechatTradeTypeEnum, String appid, Map<String, String> map) throws Exception {
PayWechatResponse payWechatResponse = new PayWechatResponse();
switch (wechatTradeTypeEnum) {
case APP:
payWechatResponse.setAppResponse(generatePayWechatAppResponse(appid, map));
break;
case JSAPI:
payWechatResponse.setJsapiResponse(generatePayWechatJsapiResponse(appid, map));
break;
case NATIVE:
break;
case MWEB:
break;
default:
break;
}
return payWechatResponse;
}
private PayWechatJsapiResponse generatePayWechatJsapiResponse (String appid, Map<String, String> map) throws Exception{
// 对于微信返回的参数再签名
Map<String, String> pkg = new HashMap<>();
pkg.put("appId", appid);
pkg.put("timeStamp", String.valueOf(System.currentTimeMillis()/1000));
pkg.put("nonceStr", WXPayUtil.generateNonceStr());
pkg.put("package", "prepay_id=" + map.get(WechatConstant.PREPAY_ID));
pkg.put("signType", wechatApps.get(appid).getSignType());
String sign = WXPayUtil.generateSignature(pkg, wechatApps.get(appid).getAppSecret(), WechatSignTypeEnum.valueOf(wechatApps.get(appid).getSignType()));
// 将再签名后的数据响应出去
PayWechatJsapiResponse payWechatJsapiResponse = new PayWechatJsapiResponse();
payWechatJsapiResponse.setAppid(appid);
payWechatJsapiResponse.setTimeStamp(pkg.get("timeStamp"));
payWechatJsapiResponse.setNonceStr(pkg.get("nonceStr"));
payWechatJsapiResponse.setPkg(pkg.get("package"));
payWechatJsapiResponse.setSignType(wechatApps.get(appid).getSignType());
payWechatJsapiResponse.setPaySign(sign);
payWechatJsapiResponse.setMchId(wechatApps.get(appid).getMchId());
payWechatJsapiResponse.setPrepayId(map.get(WechatConstant.PREPAY_ID));
return payWechatJsapiResponse;
}
private PayWechatAppResponse generatePayWechatAppResponse (String appid, Map<String, String> map) throws Exception{
// 对于微信返回的参数再签名
Map<String, String> pkg = new HashMap<>();
pkg.put("appid", appid);
pkg.put("partnerid", wechatApps.get(appid).getMchId());
pkg.put("prepayid", map.get(WechatConstant.PREPAY_ID));
pkg.put("package", "Sign=WXPay");
pkg.put("noncestr", WXPayUtil.generateNonceStr());
pkg.put("timestamp", String.valueOf(System.currentTimeMillis()/1000));
String sign = WXPayUtil.generateSignature(pkg, wechatApps.get(appid).getAppSecret(), WechatSignTypeEnum.valueOf(wechatApps.get(appid).getSignType()));
// 将再签名后的数据响应出去
PayWechatAppResponse payWechatAppResponse = new PayWechatAppResponse();
payWechatAppResponse.setAppid(appid);
payWechatAppResponse.setPartnerid(wechatApps.get(appid).getMchId());
payWechatAppResponse.setPrepayid(map.get(WechatConstant.PREPAY_ID));
payWechatAppResponse.setPkg(pkg.get("package"));
payWechatAppResponse.setNoncestr(pkg.get("noncestr"));
payWechatAppResponse.setTimestamp(pkg.get("timestamp"));
payWechatAppResponse.setSign(sign);
return payWechatAppResponse;
}
}
2.微信支付回调
@RestController
@RequestMapping(value = "/pay")
@Slf4j
public class PayController {
@Autowired
private WechatService wechatService;
@PostMapping(value = "/wechatCallback")
public Result<Object> payWechatCallback (@RequestBody PayWechatCallbackRequest payWechatCallbackRequest) {
try {
log.info("微信支付回调接口开始执行,请求参数:{}", JSON.toJSON(payWechatCallbackRequest));
wechatService.payCallback(payWechatCallbackRequest);
log.info("微信支付回调接口执行成功,请求参数:{}", JSON.toJSON(payWechatCallbackRequest));
return Result.getSuccess();
} catch (ResponseException e) {
log.error("微信支付回调接口执行失败1,请求参数:{},异常原因:{}", JSON.toJSON(payWechatCallbackRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(e.getCode(), e.getMessage());
} catch (Exception e) {
log.error("微信支付回调接口执行失败2,请求参数:{},异常原因:{}", JSON.toJSON(payWechatCallbackRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(ResultCode.INTERNAL_SERVER_ERROR);
}
}
}
@Service
@Slf4j
public class WechatService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private Map<String, WechatAppConfig> wechatApps;
public void payCallback (PayWechatCallbackRequest payWechatCallbackRequest) throws Exception {
String xml = payWechatCallbackRequest.getXml();
Map<String, String> map = getValidResponseMap(xml);
// todo 校验订单是否存在
String outTradeNo = map.get(WechatConstant.OUT_TRADE_NO);
// todo 校验订单是否已支付
// todo 校验支付金额
Integer totalFee = Integer.valueOf(map.get(WechatConstant.TOTAL_FEE));
// todo 修改支付和订单的状态
// 微信的支付交易流水号
String transactionId = map.get(WechatConstant.TRANSACTION_ID);
// 用户支付时间
String payTime = map.get(WechatConstant.TIME_END);
}
private Map<String, String> getValidResponseMap (String xml) throws Exception {
Map<String, String> map = WXPayUtil.xmlToMap(xml);
String returnCode = map.get(WechatConstant.RETURN_CODE);
if (!StringUtils.isEmpty(returnCode) && WechatConstant.SUCCESS.equals(returnCode)) {
String appid = map.get(WechatConstant.APPID);
WechatAppConfig wechatAppConfig = wechatApps.get(appid);
if (!Objects.isNull(wechatAppConfig)) {
log.error("微信支付回调返回的appid错误");
throw new ResponseException(ResultCode.WECHAT_PAY_CALLBACK_APPID_ERROR);
}
boolean isSignatureValid = WXPayUtil.isSignatureValid(map, wechatAppConfig.getAppSecret(), WechatSignTypeEnum.valueOf(wechatAppConfig.getSignType()));
if (isSignatureValid) {
return map;
} else {
throw new ResponseException(ResultCode.WECHAT_PAY_CALLBACK_SIGNATURE_VALID_ERROR);
}
} else {
log.error("微信支付回调返回的return_code字段错误");
throw new ResponseException(ResultCode.WECHAT_PAY_CALLBACK_RETURN_CODE_ERROR);
}
}
}
3.微信支付退款
@RestController
@RequestMapping(value = "/pay")
@Slf4j
public class RefundController {
@Autowired
private RefundService refundService;
@PostMapping(value = "/orderRefund")
public Result<Object> orderRefund (@RequestBody OrderRefundRequest orderRefundRequest) {
try {
log.info("订单退款接口开始执行,请求参数:{}", JSON.toJSONString(orderRefundRequest));
refundService.orderRefund(orderRefundRequest);
log.info("订单退款接口执行完成,请求参数:{}", JSON.toJSONString(orderRefundRequest));
return Result.getSuccess();
} catch (ResponseException e) {
log.error("订单退款接口执行失败1,请求参数:{},异常原因:{}", JSON.toJSONString(orderRefundRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(e.getCode(), e.getMessage());
} catch (Exception e) {
log.error("订单退款接口执行失败2,请求参数:{},异常原因:{}", JSON.toJSONString(orderRefundRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(ResultCode.INTERNAL_SERVER_ERROR);
}
}
}
@Service
@Slf4j
public class RefundService {
@Autowired
private WechatService wechatService;
// 订单退款
public void orderRefund (OrderRefundRequest orderRefundRequest) {
String orderNo = orderRefundRequest.getOrderNo();
Integer curRefundFee = orderRefundRequest.getFee();
// 获取该订单的支付详情
OrderPayInfo payInfo = getOrderPayInfo(orderNo);
// 获取该订单的支付金额
int orderPaidFee = getOrderPaidFee(orderNo);
// 获取该订单的已退金额
int orderRefundedFee = getOrderRefundedFee(orderNo);
if (curRefundFee+orderRefundedFee > orderPaidFee) {
// 订单可退余额不足
log.info("订单可退余额不足,退款请求参数:{}", JSON.toJSONString(orderRefundRequest));
throw new ResponseException(ResultCode.REFUND_FEE_NOT_ENOUGH_ERROR);
}
// 获取该订单的支付方式
PayTypeEnum payTypeEnum = getOrderPayTypeEnum(orderNo);
switch (payTypeEnum) {
case WECHAT_PAY:
wechatService.refund(payInfo, curRefundFee);
break;
case ALI_PAY:
break;
case HUAWEI_PAY:
break;
case APPLE_PAY:
break;
case MI_PAY:
break;
default:
break;
}
}
// 获取订单的支付详情
private OrderPayInfo getOrderPayInfo (String orderNo) {
return new OrderPayInfo();
}
// 获取订单的支付金额
private int getOrderPaidFee (String orderNo) {
return 0;
}
// 获取订单的已退金额
private int getOrderRefundedFee (String orderNo) {
return 0;
}
// 获取订单的支付方式
private PayTypeEnum getOrderPayTypeEnum (String orderNo) {
return PayTypeEnum.WECHAT_PAY;
}
}
@Service
@Slf4j
public class WechatService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private Map<String, WechatAppConfig> wechatApps;
@Autowired
private HttpXmlRequest httpXmlRequest;
// 微信退款
public void refund (OrderPayInfo payInfo, Integer refundFee) {
Map<String, String> refundXmlMap = getRefundXmlMap(payInfo, refundFee);
String xml;
try {
xml = WXPayUtil.mapToXml(refundXmlMap);
} catch (Exception e) {
log.error("微信退款生成退款xml数据异常");
e.printStackTrace();
throw new ResponseException(ResultCode.WECHAT_REFUND_GENERATE_XML_ERROR);
}
String appid = "";
WechatAppConfig wechatApp = wechatApps.get(appid);
String mchId = wechatApp.getMchId();
String certPath = wechatApp.getCert();
try {
Map<String, String> map = httpXmlRequest.postXmlWithCert(certPath, mchId, wechatConfig.getRefundOrderUrl(), xml);
if (WechatConstant.SUCCESS.equals(map.get(WechatConstant.RETURN_CODE)) && WechatConstant.SUCCESS.equals(map.get(WechatConstant.RESULT_CODE))) {
// todo 退款申请成功,记录退款数据到db
}
} catch (Exception e) {
log.error("微信退款请求微信接口失败");
e.printStackTrace();
throw new ResponseException(ResultCode.WECHAT_REFUND_ERROR);
}
}
private Map<String, String> getRefundXmlMap (OrderPayInfo payInfo, Integer curRefundFee) {
// 从订单支付详payInfo中获取appid
String appid = "";
WechatAppConfig wechatApp = wechatApps.get(appid);
String signType = wechatApp.getSignType();
String appSecret = wechatApp.getAppSecret();
// 从订单支付详payInfo中获取支付时微信的transaction_id
String payTransactionId = "";
// 生成退款时,请求微信的 out_refund_no
String outRefundNo = "";
// 订单的支付金额,单位为分
String totalFee = "100";
Map<String, String> reqData = new HashMap<>();
reqData.put(WechatConstant.APPID, appid);
reqData.put(WechatConstant.MCH_ID, wechatApp.getMchId());
reqData.put(WechatConstant.NONCE_STR, WXPayUtil.generateNonceStr());
reqData.put(WechatConstant.TRANSACTION_ID, payTransactionId);
reqData.put(WechatConstant.OUT_REFUND_NO, outRefundNo);
reqData.put(WechatConstant.TOTAL_FEE, totalFee);
reqData.put(WechatConstant.REFUND_FEE, String.valueOf(curRefundFee));
if (WechatConstant.MD5.equals(signType)) {
reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.MD5);
}
else if (WechatConstant.HMACSHA256.equals(signType)) {
reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.HMACSHA256);
}
try {
reqData.put(WechatConstant.SIGN, WXPayUtil.generateSignature(reqData, appSecret, WechatSignTypeEnum.valueOf(signType)));
} catch (Exception e) {
log.warn(e.getMessage());
e.printStackTrace();
}
return reqData;
}
}
微信支付官网demo工具类
package com.wazk.pay.utils;
import com.wazk.pay.constant.WechatConstant;
import com.wazk.pay.constant.WechatSignTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.ssl.SSLContexts;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.math.BigDecimal;
import java.security.*;
import java.util.*;
@Slf4j
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 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 = newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
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 TransformerException, ParserConfigurationException {
Document document = 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 e) {
e.printStackTrace();
}
log.info("获取微信支付的请求参数XML:{}", output);
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, WechatSignTypeEnum.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, WechatSignTypeEnum signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WechatConstant.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(WechatConstant.SIGN)) {
return false;
}
String sign = data.get(WechatConstant.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, WechatSignTypeEnum.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, WechatSignTypeEnum signType) throws Exception {
if (!data.containsKey(WechatConstant.SIGN)) {
return false;
}
String sign = data.get(WechatConstant.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, WechatSignTypeEnum.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, WechatSignTypeEnum 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(WechatConstant.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 (WechatSignTypeEnum.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
} else if (WechatSignTypeEnum.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();
}
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();
}
/**
* 获取请求文体
*
* @param request
* @return
* @throws IOException
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
/**
* @param return_code:
* @param return_msg:
* @Description: 通过xml给微信返回数据
* @Author: wazk2008
* @Date: 2021/10/15 4:29 下午
* @return: java.lang.String
**/
public static String setXml(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code + "]]>" +
"</return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
}
/**
* @Description: 初始化证书获取 SSLConnectionSocketFactory
* @Author: wazk2008
* @Date: 2021/10/27 6:48 下午
* @param cert:
* @param mchId:
* @return: org.apache.http.conn.ssl.SSLConnectionSocketFactory
**/
public static SSLConnectionSocketFactory initCert(String cert, String mchId) throws Exception {
// 加载本地的证书进行https加密传输
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream inputStream = new FileInputStream(cert);
try {
// 加载证书密码,默认为商户ID
keyStore.load(inputStream, mchId.toCharArray());
} finally {
inputStream.close();
}
SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1.2"}, null, new DefaultHostnameVerifier());
return sslcsf;
}
/**
* @Description: 解析xml的输入流为map
* @Author: wazk2008
* @Date: 2021/10/28 2:11 下午
* @param inputStream:
* @return: java.util.Map<java.lang.String,java.lang.String>
**/
public static Map<String, String> parseXml(InputStream inputStream) throws IOException {
SortedMap<String, String> map = new TreeMap<>();
try {
// 获取request输入流
SAXReader reader = new SAXReader();
org.dom4j.Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素所有节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element element : elementList) {
map.put(element.getName(), element.getText());
}
} catch (Exception e) {
e.printStackTrace();
log.info("*****微信工具类:解析xml异常*****");
} finally {
// 释放资源
inputStream.close();
}
return map;
}
private static Cipher cipher = null;
/**
* @Description: 初始化解码器
* @Author: wazk2008
* @Date: 2021/10/28 5:01 下午
* @param key:
* @return: void
**/
private static synchronized void init(String key){
if (cipher != null) {
return;
}
String md5Key = DigestUtils.md5DigestAsHex(key.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(md5Key.getBytes(), "AES");
Security.addProvider(new BouncyCastleProvider());
try {
cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
/**
* @Description: 微信退款回调时,解析reqInfo加密数据
* @Author: wazk2008
* @Date: 2021/10/28 4:36 下午
* @param reqInfo:
* @return: java.lang.String
**/
public static String parseReqInfo (String reqInfo, String key) throws IllegalBlockSizeException, BadPaddingException {
Base64.Decoder decoder = Base64.getDecoder();
byte[] base64ByteArr = decoder.decode(reqInfo);
init(key);
String result = new String(cipher.doFinal(base64ByteArr));
return result;
}
}