SpringBoot集成微信支付微信退款
微信支付
备注:本次支付接入的是微信支付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 {@Autowiredprivate 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 {@Autowiredprivate WechatConfig wechatConfig;@Autowiredprivate Map<String, WechatAppConfig> wechatApps;@Autowiredprivate 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对应的mapMap<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 {@Autowiredprivate 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 {@Autowiredprivate WechatConfig wechatConfig;@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate WechatConfig wechatConfig;@Autowiredprivate Map<String, WechatAppConfig> wechatApps;@Autowiredprivate 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中获取appidString appid = "";WechatAppConfig wechatApp = wechatApps.get(appid);String signType = wechatApp.getSignType();String appSecret = wechatApp.getAppSecret();// 从订单支付详payInfo中获取支付时微信的transaction_idString payTransactionId = "";// 生成退款时,请求微信的 out_refund_noString 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 {// 加载证书密码,默认为商户IDkeyStore.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;}}
SpringBoot集成微信支付微信退款相关推荐
- SpringBoot集成支付宝支付支付宝退款
支付宝支付 阅官方文档 https://opendocs.alipay.com/open/02e7gq 个人支付案例git地址[微信支付/支付宝支付/华为支付/苹果支付/小米支付]:https://g ...
- springboot之微信支付与退款
基于springboot实现小程序微信支付与退款 最近需要再写小程序商城,无可避免的需要用到微信支付与商品售后退款等功能.于是研究了一些大佬的代码之后整合出了这个比较简单的微信支付与退款. 相关内容引 ...
- php微信支付分取消订单,PHP实现微信支付和退款
这次给大家带来PHP实现微信支付和退款,PHP实现微信支付和退款的注意事项有哪些,下面就是实战案例,一起来看一下. 之前有写过几篇文章将微信支付和退款: 1.PHP实现微信支付(jsapi支付)流程 ...
- 关于微信支付的退款那些事
关于微信支付的退款那些事 微信支付的退款 需要双向证书 一个是操作人的电脑上 需要安装的证书 以p12为结尾的 另外一个证书是2个,需要放到服务器上 微信支付的退款,在请求接口的时候,会在发起人的电 ...
- ThinkPHP 微信支付及退款
目录 微信支付 微信退款 1.以下代码修改完自己的 2.appid 3.商户号 4.商户密钥 微信支付 //微信支付public function index(){//接收用户下单信息$data = ...
- java微信支付v3系列——8.微信支付之退款成功回调
目录 java微信支付v3系列--1.微信支付准备工作 java微信支付v3系列--2.微信支付基本配置 java微信支付v3系列--3.订单创建准备操作 java微信支付v3系列--4.创建订单的封 ...
- 微信支付-“申请退款”接口遇到curl出错,错误码:58解决方案
微信支付后 退款,接口遇到curl出错,错误码:58 这个问题基本上是证书没对应上(微信支付不需要申请证书,退款要用到证书),证书要去微信商户号后台(账户中心-API安全-申请证书)下载,已经下载过忘 ...
- 微信支付——微信退款实战教程(Java版)
微信支付之微信申请退款实战(Java版) 微信支付业务场景 一.注意事项 二.微信支付退款案例 1.微信退款案例 二.微信支付官方说明 总结 微信支付业务场景 当交易发生之后一年内,由于买家或者卖家的 ...
- 用java实现微信支付,退款,部分退款服务端
由于公司业务需要,最近搞微信退款功能,今天抽空在此记录一下,以后用到也可以到这来看一眼.废话不多说,进入正题. 微信支付以及付款呢,先要有个证书,不清楚的,还要有证书,可以去微信平台看证书怎么下载 h ...
- 【SpringBoot学习】46、SpringBoot 集成 Uniapp 实现微信公众号授权登录
文章目录 一.公众号环境搭建 二.Spring Boot 集成微信公众号 1.application.yml 微信配置 2.控制层接口 三.Uniapp 实现授权登录 一.公众号环境搭建 本篇文章使用 ...
最新文章
- 三、python中最基础的文件处理汇总
- 40多个漂亮的网页表单设计实例
- Cisco OSPF常见问题
- docker 安装部署 activemq ActiveMQ
- SAP Spartacus的home page navigation逻辑
- 软件测试流程进阶----两年软件测试总结[转]
- .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类
- mysql decimal型转化为float_5分钟搞懂MySQL数据类型之数值型DECIMAL类型
- Centos 6.5安装MySQL-python
- 论文翻译——FingerSound:Recognizing unistroke thumb gestures using a ring
- c语言程序怎么打分数,用C语言编程平均分数
- access设计视图打不开_铁路桥梁BIM程序的设计与实现
- (jQuery)插件开发模式
- rsyslog及loganalyzer
- modbusx协议讲解
- 添加自签发的 SSL 证书为受信任的根证书
- oracle中sql查询增加自增序列号
- HTML资源未找到,加载资源失败:服务器响应状态为404(未找到)
- 字符串使用split()方法截取时的空字符串问题
- 为什么说现在IT toB发展的拐点
热门文章
- JS 报错getElementsByClassName.appendChild报错“Uncaught TypeError: s.appendChild is not a function”
- SSO (Single Sign On)
- Python笔记 · 鸭子类型 / Duck Typing
- 阿里算法实习生面试回忆
- 用edge调试安卓或者手机app的样式
- 使用免费绿色工具chfs,将文件夹共享成网盘
- 爬虫实战之全站爬取拉勾网职位信息
- 论文投稿指南——中文核心期刊推荐(物理学)
- 网络购物需谨慎 “闲鱼”与“咸鱼“仅一个链接的距离
- arduino 天下第一(暴论) -- 智能猫眼与 SDDC 连接器移植到 arduino 上