700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 微信支付回调退款

微信支付回调退款

时间:2019-10-02 08:43:08

相关推荐

微信支付回调退款

容灾

package com.mons.utils;/*** 域名管理,实现主备域名自动切换*/public abstract interface IWXPayDomain {/*** 上报域名网络状况* @param domain 域名。 比如:api.mch.* @param elapsedTimeMillis 耗时* @param ex 网络请求中出现的异常。* null表示没有异常* ConnectTimeoutException,表示建立网络连接异常* UnknownHostException, 表示dns解析异常*/abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);/*** 获取域名* @param config 配置* @return 域名*/abstract DomainInfo getDomain(final WXPayConfig config);static class DomainInfo{public String domain; //域名public boolean primaryDomain;//该域名是否为主域名。例如:api.mch.为主域名public DomainInfo(String domain, boolean primaryDomain) {this.domain = domain;this.primaryDomain = primaryDomain;}@Overridepublic String toString() {return "DomainInfo{" +"domain='" + domain + '\'' +", primaryDomain=" + primaryDomain +'}';}}}

微信sdk方法

package com.mons.utils;import javax.servlet.http.HttpServletRequest;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;/*** 微信公用工具类*/public class WeiXinOrderUtil {/*@Autowiredprivate WechatConfig wechatConfig;*//*** 组装统一下单参数* @return*//*public String buildWechatOrderParam(WechatOrder wechatOrder,HttpServletRequest request) throws Exception{String payKey = wechatConfig.getPaykey();String signType = wechatConfig.getSignType();SortedMap<String, String> signMap = new TreeMap<String, String>();long timeStamp = PayUtils.getUnixTime(new Date());wechatOrder.setAppid(wechatConfig.getWechatAppId());wechatOrder.setTrade_type(wechatConfig.getTradeType());//构建参数返回前台 请求支付接口String prepayId = createWechatOrder(wechatOrder,request);signMap.put("appId",wechatOrder.getAppid());signMap.put("timeStamp", timeStamp+"");signMap.put("nonceStr", wechatOrder.getNonce_str());signMap.put("package", "prepay_id="+prepayId);signMap.put("signType", signType);String paySign = PayUtils.getSign(signMap, payKey);signMap.put("pg", prepayId);signMap.put("paySign", paySign);signMap.put("result", "success");String json = JSON.toJSONString(signMap);JSONObject returnJson=JSONObject.parseObject(json);return returnJson.toJSONString();}*//*** 创建下单签名 包含商品信息* @return*//*public String createWechatSign(WechatOrder wechatOrder){String mch_id = wechatConfig.getMchId();String notify_url = wechatConfig.getNotifyUrl();String device_info = wechatConfig.getDeviceInfo();String payKey = wechatConfig.getPaykey();//将商品信息打包SortedMap<String, String> parameters = new TreeMap<String, String>();parameters.put("appid", wechatOrder.getAppid());//公众号id 这地方一定要小写并跟下面xml文件对应都是小写parameters.put("mch_id",mch_id);//商户IDparameters.put("device_info", device_info);parameters.put("body", wechatOrder.getOrderName());//名称parameters.put("trade_type", wechatOrder.getTrade_type());parameters.put("nonce_str", wechatOrder.getNonce_str());//随机数parameters.put("notify_url", notify_url);parameters.put("out_trade_no", wechatOrder.getOut_trade_no());parameters.put("total_fee", wechatOrder.getTotal_fee()+"");// parameters.put("spbill_create_ip", spbill_create_ip );parameters.put("openid", wechatOrder.getOpenid());//根据上述的数据生成预支付订单号的前面signreturn PayUtils.getSign(parameters, payKey);}*//*** 创建微信订单 请求微信接口下单* @return*//*public String createWechatOrder(WechatOrder wechatOrder,HttpServletRequest request) throws Exception{String mch_id = wechatConfig.getMchId();String notify_url = wechatConfig.getNotifyUrl();String device_info = wechatConfig.getDeviceInfo();String createOrderURL = wechatConfig.getCreateOrderUrl();//生成统一支付接口数据String xml = "<xml>"+"<appid>"+wechatOrder.getAppid()+"</appid>"+"<body>"+wechatOrder.getOrderName()+"</body>"+"<device_info>"+device_info+"</device_info>"+"<mch_id>"+mch_id+"</mch_id>"+"<nonce_str>"+wechatOrder.getNonce_str()+"</nonce_str>"+"<notify_url>"+notify_url+"</notify_url>"+"<openid>"+wechatOrder.getOpenid()+"</openid>"+"<out_trade_no>"+wechatOrder.getOut_trade_no()+"</out_trade_no>"+"<total_fee>"+wechatOrder.getTotal_fee()+"</total_fee>"+"<trade_type>"+wechatOrder.getTrade_type()+"</trade_type>"+"<sign>"+createWechatSign(wechatOrder)+"</sign>"+"</xml>";//调用统一支付接口String result = PayUtils.httpsRequest(createOrderURL, "POST", xml);System.out.println("-----------------------------统一下单结果---------------------------");System.out.println(result);Map<String, String> resultMap = null;resultMap=PayUtils.getH5PayMap(result,request);return resultMap.get("prepay_id"); //预支付ID,保存到数据库中}*//*** 获取 WeixinOauth2Token* @return*//*public WeixinOauth2Token getWeixinOauth2Token(String code){//获得微信公众号的唯一标识String appId = wechatConfig.getWechatAppId();String appSecret = wechatConfig.getWechatAppSecret();return AdvancedUtil.getOauth2AccessToken(appId, appSecret, code);}*//*** 解析微信支付回调的结果* @return*/public static String getOrderReturnStream(HttpServletRequest request) throws IOException {InputStream inStream = request.getInputStream();ByteArrayOutputStream outSteam = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len = 0;while ((len = inStream.read(buffer)) != -1) {outSteam.write(buffer, 0, len);}outSteam.close();inStream.close();return new String(outSteam.toByteArray(),"utf-8");}}package com.mons.utils;import java.util.HashMap;import java.util.Map;public class WXPay {private WXPayConfig config;private WXPayConstants.SignType signType;private boolean autoReport;private boolean useSandbox;private String notifyUrl;private WXPayRequest wxPayRequest;public WXPay(final WXPayConfig config) throws Exception {this(config, null, true, false);}public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception {this(config, null, autoReport, false);}public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{this(config, null, autoReport, useSandbox);}public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception {this(config, notifyUrl, true, false);}public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception {this(config, notifyUrl, autoReport, false);}public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {this.config = config;this.notifyUrl = notifyUrl;this.autoReport = autoReport;this.useSandbox = useSandbox;if (useSandbox) {this.signType = WXPayConstants.SignType.MD5; // 沙箱环境}else {this.signType = WXPayConstants.SignType.MD5;}this.wxPayRequest = new WXPayRequest(config);}private void checkWXPayConfig() throws Exception {if (this.config == null) {throw new Exception("config is null");}if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) {throw new Exception("appid in config is empty");}if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) {throw new Exception("appid in config is empty");}if (this.config.getCertStream() == null) {throw new Exception("cert stream in config is empty");}if (this.config.getWXPayDomain() == null){throw new Exception("config.getWXPayDomain() is null");}if (this.config.getHttpConnectTimeoutMs() < 10) {throw new Exception("http connect timeout is too small");}if (this.config.getHttpReadTimeoutMs() < 10) {throw new Exception("http read timeout is too small");}}/*** 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>* 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口** @param reqData* @return* @throws Exception*/public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {reqData.put("appid", config.getAppID());reqData.put("mch_id", config.getMchID());reqData.put("nonce_str", WXPayUtil.generateNonceStr());if (WXPayConstants.SignType.MD5.equals(this.signType)) {reqData.put("sign_type", WXPayConstants.MD5);}else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {reqData.put("sign_type", WXPayConstants.HMACSHA256);}reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));return reqData;}/*** 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。** @param reqData 向wxpay post的请求数据* @return 签名是否有效* @throws Exception*/public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {// 返回数据的签名方式和请求中给定的签名方式是一致的return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);}/*** 判断支付结果通知中的sign是否有效** @param reqData 向wxpay post的请求数据* @return 签名是否有效* @throws Exception*/public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);WXPayConstants.SignType signType;if (signTypeInData == null) {signType = WXPayConstants.SignType.MD5;}else {signTypeInData = signTypeInData.trim();if (signTypeInData.length() == 0) {signType = WXPayConstants.SignType.MD5;}else if ( WXPayConstants.MD5.equals(signTypeInData)) {signType = WXPayConstants.SignType.MD5;}else if ( WXPayConstants.HMACSHA256.equals(signTypeInData)) {signType = WXPayConstants.SignType.HMACSHA256;}else {throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));}}return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);}/*** 不需要证书的请求* @param urlSuffix String* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 超时时间,单位是毫秒* @param readTimeoutMs 超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public String requestWithoutCert(String urlSuffix, Map<String, String> reqData,int connectTimeoutMs, int readTimeoutMs) throws Exception {String msgUUID = reqData.get("nonce_str");String reqBody = WXPayUtil.mapToXml(reqData);String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);return resp;}/*** 需要证书的请求* @param urlSuffix String* @param reqData 向wxpay post的请求数据 Map* @param connectTimeoutMs 超时时间,单位是毫秒* @param readTimeoutMs 超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public String requestWithCert(String urlSuffix, Map<String, String> reqData,int connectTimeoutMs, int readTimeoutMs) throws Exception {String msgUUID= reqData.get("nonce_str");String reqBody = WXPayUtil.mapToXml(reqData);String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);return resp;}/*** 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。* @param xmlStr API返回的XML格式数据* @return Map类型数据* @throws Exception*/public Map<String, String> processResponseXml(String xmlStr) throws Exception {String RETURN_CODE = "return_code";String return_code;Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);if (respData.containsKey(RETURN_CODE)) {return_code = respData.get(RETURN_CODE);}else {throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));}if (return_code.equals(WXPayConstants.FAIL)) {return respData;}else if (return_code.equals( WXPayConstants.SUCCESS)) {if (this.isResponseSignatureValid(respData)) {return respData;}else {throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));}}else {throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));}}/*** 作用:提交刷卡支付<br>* 场景:刷卡支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> microPay(Map<String, String> reqData) throws Exception {return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:提交刷卡支付<br>* 场景:刷卡支付* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX;}else {url = WXPayConstants.MICROPAY_URL_SUFFIX;}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 提交刷卡支付,针对软POS,尽可能做成功* 内置重试机制,最多60s* @param reqData* @return* @throws Exception*/public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception {return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());}/*** 提交刷卡支付,针对软POS,尽可能做成功* 内置重试机制,最多60s* @param reqData* @param connectTimeoutMs* @return* @throws Exception*/public Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception {int remainingTimeMs = 60*1000;long startTimestampMs = 0;Map<String, String> lastResult = null;Exception lastException = null;while (true) {startTimestampMs = WXPayUtil.getCurrentTimestampMs();int readTimeoutMs = remainingTimeMs - connectTimeoutMs;if (readTimeoutMs > 1000) {try {lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);String returnCode = lastResult.get("return_code");if (returnCode.equals("SUCCESS")) {String resultCode = lastResult.get("result_code");String errCode = lastResult.get("err_code");if (resultCode.equals("SUCCESS")) {break;}else {// 看错误码,若支付结果未知,则重试提交刷卡支付if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {remainingTimeMs = remainingTimeMs - (int)( WXPayUtil.getCurrentTimestampMs() - startTimestampMs);if (remainingTimeMs <= 100) {break;}else {WXPayUtil.getLogger().info("microPayWithPos: try micropay again");if (remainingTimeMs > 5*1000) {Thread.sleep(5*1000);}else {Thread.sleep(1*1000);}continue;}}else {break;}}}else {break;}}catch (Exception ex) {lastResult = null;lastException = ex;}}else {break;}}if (lastResult == null) {throw lastException;}else {return lastResult;}}/*** 作用:统一下单<br>* 场景:公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:统一下单<br>* 场景:公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> unifiedOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX;}else {url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX;}if(this.notifyUrl != null) {reqData.put("notify_url", this.notifyUrl);}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 作用:查询订单<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:查询订单<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据 int* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX;}else {url = WXPayConstants.ORDERQUERY_URL_SUFFIX;}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 作用:撤销订单<br>* 场景:刷卡支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> reverse(Map<String, String> reqData) throws Exception {return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:撤销订单<br>* 场景:刷卡支付<br>* 其他:需要证书* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX;}else {url = WXPayConstants.REVERSE_URL_SUFFIX;}String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 作用:关闭订单<br>* 场景:公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:关闭订单<br>* 场景:公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> closeOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX;}else {url = WXPayConstants.CLOSEORDER_URL_SUFFIX;}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 作用:申请退款<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> refund(Map<String, String> reqData) throws Exception {return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:申请退款<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>* 其他:需要证书* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX;}else {url = WXPayConstants.REFUND_URL_SUFFIX;}String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 作用:退款查询<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:退款查询<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX;}else {url = WXPayConstants.REFUNDQUERY_URL_SUFFIX;}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:对账单下载<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>* 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,*其中return_code为`SUCCESS`,data为对账单数据。* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return 经过封装的API返回数据* @throws Exception*/public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX;}else {url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX;}String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();Map<String, String> ret;// 出现错误,返回XML数据if (respStr.indexOf("<") == 0) {ret = WXPayUtil.xmlToMap(respStr);}else {// 正常返回csv数据ret = new HashMap<String, String>();ret.put("return_code", WXPayConstants.SUCCESS);ret.put("return_msg", "ok");ret.put("data", respStr);}return ret;}/*** 作用:交易保障<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> report(Map<String, String> reqData) throws Exception {return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:交易保障<br>* 场景:刷卡支付、公共号支付、扫码支付、APP支付* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX;}else {url = WXPayConstants.REPORT_URL_SUFFIX;}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return WXPayUtil.xmlToMap(respXml);}/*** 作用:转换短链接<br>* 场景:刷卡支付、扫码支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:转换短链接<br>* 场景:刷卡支付、扫码支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX;}else {url = WXPayConstants.SHORTURL_URL_SUFFIX;}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}/*** 作用:授权码查询OPENID接口<br>* 场景:刷卡支付* @param reqData 向wxpay post的请求数据* @return API返回数据* @throws Exception*/public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());}/*** 作用:授权码查询OPENID接口<br>* 场景:刷卡支付* @param reqData 向wxpay post的请求数据* @param connectTimeoutMs 连接超时时间,单位是毫秒* @param readTimeoutMs 读超时时间,单位是毫秒* @return API返回数据* @throws Exception*/public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {String url;if (this.useSandbox) {url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX;}else {url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX;}String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);return this.processResponseXml(respXml);}} // end classpackage com.mons.utils;import org.apache.http.client.HttpClient;/*** 常量*/public class WXPayConstants {public enum SignType {MD5, HMACSHA256}public static final String DOMAIN_API = "api.mch.";public static final String DOMAIN_API2 = "api2.mch.";public static final String DOMAIN_APIHK = "apihk.mch.";public static final String DOMAIN_APIUS = "apius.mch.";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 static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";public static final String USER_AGENT = WXPAYSDK_VERSION +" (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();public static final String MICROPAY_URL_SUFFIX= "/pay/micropay";public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";public static final String ORDERQUERY_URL_SUFFIX = "/pay/orderquery";public static final String REVERSE_URL_SUFFIX= "/secapi/pay/reverse";public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder";public static final String REFUND_URL_SUFFIX = "/secapi/pay/refund";public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery";public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";public static final String REPORT_URL_SUFFIX = "/payitil/report";public static final String SHORTURL_URL_SUFFIX= "/tools/shorturl";public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";// sandboxpublic static final String SANDBOX_MICROPAY_URL_SUFFIX= "/sandboxnew/pay/micropay";public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";public static final String SANDBOX_ORDERQUERY_URL_SUFFIX = "/sandboxnew/pay/orderquery";public static final String SANDBOX_REVERSE_URL_SUFFIX= "/sandboxnew/secapi/pay/reverse";public static final String SANDBOX_CLOSEORDER_URL_SUFFIX = "/sandboxnew/pay/closeorder";public static final String SANDBOX_REFUND_URL_SUFFIX = "/sandboxnew/secapi/pay/refund";public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX = "/sandboxnew/pay/refundquery";public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";public static final String SANDBOX_REPORT_URL_SUFFIX = "/sandboxnew/payitil/report";public static final String SANDBOX_SHORTURL_URL_SUFFIX= "/sandboxnew/tools/shorturl";public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";}package com.mons.utils;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.HttpClient;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.methods.HttpPost;import org.apache.http.config.RegistryBuilder;import org.apache.http.conn.socket.ConnectionSocketFactory;import org.apache.http.conn.socket.PlainConnectionSocketFactory;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.impl.conn.BasicHttpClientConnectionManager;import org.apache.http.util.EntityUtils;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadFactory;/*** 交易保障*/public class WXPayReport {public static class ReportInfo {/*** 布尔变量使用int。0为false, 1为true。*/// 基本信息private String version = "v1";private String sdk = WXPayConstants.WXPAYSDK_VERSION;private String uuid; // 交易的标识private long timestamp; // 上报时的时间戳,单位秒private long elapsedTimeMillis; // 耗时,单位 毫秒// 针对主域名private String firstDomain; // 第1次请求的域名private boolean primaryDomain; //是否主域名private int firstConnectTimeoutMillis; // 第1次请求设置的连接超时时间,单位 毫秒private int firstReadTimeoutMillis; // 第1次请求设置的读写超时时间,单位 毫秒private int firstHasDnsError; // 第1次请求是否出现dns问题private int firstHasConnectTimeout; // 第1次请求是否出现连接超时private int firstHasReadTimeout; // 第1次请求是否出现连接超时public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {this.uuid = uuid;this.timestamp = timestamp;this.elapsedTimeMillis = elapsedTimeMillis;this.firstDomain = firstDomain;this.primaryDomain = primaryDomain;this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;this.firstReadTimeoutMillis = firstReadTimeoutMillis;this.firstHasDnsError = firstHasDnsError?1:0;this.firstHasConnectTimeout = firstHasConnectTimeout?1:0;this.firstHasReadTimeout = firstHasReadTimeout?1:0;}@Overridepublic String toString() {return "ReportInfo{" +"version='" + version + '\'' +", sdk='" + sdk + '\'' +", uuid='" + uuid + '\'' +", timestamp=" + timestamp +", elapsedTimeMillis=" + elapsedTimeMillis +", firstDomain='" + firstDomain + '\'' +", primaryDomain=" + primaryDomain +", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis +", firstReadTimeoutMillis=" + firstReadTimeoutMillis +", firstHasDnsError=" + firstHasDnsError +", firstHasConnectTimeout=" + firstHasConnectTimeout +", firstHasReadTimeout=" + firstHasReadTimeout +'}';}/*** 转换成 csv 格式** @return*/public String toLineString(String key) {String separator = ",";Object[] objects = new Object[] {version, sdk, uuid, timestamp, elapsedTimeMillis,firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout};StringBuffer sb = new StringBuffer();for(Object obj: objects) {sb.append(obj).append(separator);}try {String sign = WXPayUtil.HMACSHA256(sb.toString(), key);sb.append(sign);return sb.toString();}catch (Exception ex) {return null;}}}private static final String REPORT_URL = "http://report.mch./wxpay/report/default";// private static final String REPORT_URL = "http://127.0.0.1:5000/test";private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000;private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000;private LinkedBlockingQueue<String> reportMsgQueue = null;private WXPayConfig config;private ExecutorService executorService;private volatile static WXPayReport INSTANCE;private WXPayReport(final WXPayConfig config) {this.config = config;reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize());// 添加处理线程executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() {public Thread newThread(Runnable r) {Thread t = Executors.defaultThreadFactory().newThread(r);t.setDaemon(true);return t;}});if (config.shouldAutoReport()) {WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());for (int i = 0; i < config.getReportWorkerNum(); ++i) {executorService.execute(new Runnable() {public void run() {while (true) {// 先用 take 获取数据try {StringBuffer sb = new StringBuffer();String firstMsg = reportMsgQueue.take();WXPayUtil.getLogger().info("get first report msg: {}", firstMsg);String msg = null;sb.append(firstMsg); //会阻塞至有消息int remainNum = config.getReportBatchSize() - 1;for (int j=0; j<remainNum; ++j) {WXPayUtil.getLogger().info("try get remain report msg");// msg = reportMsgQueue.poll(); // 不阻塞了msg = reportMsgQueue.take();WXPayUtil.getLogger().info("get remain report msg: {}", msg);if (msg == null) {break;}else {sb.append("\n");sb.append(msg);}}// 上报WXPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);}catch (Exception ex) {WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());}}}});}}}/*** 单例,双重校验,请在 JDK 1.5及更高版本中使用** @param config* @return*/public static WXPayReport getInstance(WXPayConfig config) {if (INSTANCE == null) {synchronized (WXPayReport.class) {if (INSTANCE == null) {INSTANCE = new WXPayReport(config);}}}return INSTANCE;}public void report(String uuid, long elapsedTimeMillis,String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {long currentTimestamp = WXPayUtil.getCurrentTimestamp();ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis,firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout);String data = reportInfo.toLineString(config.getKey());WXPayUtil.getLogger().info("report {}", data);if (data != null) {reportMsgQueue.offer(data);}}@Deprecatedprivate void reportSync(final String data) throws Exception {httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);}@Deprecatedprivate void reportAsync(final String data) throws Exception {new Thread(new Runnable() {public void run() {try {httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);}catch (Exception ex) {WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());}}}).start();}/*** http 请求* @param data* @param connectTimeoutMs* @param readTimeoutMs* @return* @throws Exception*/private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs) throws Exception{BasicHttpClientConnectionManager connManager;connManager = new BasicHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build(),null,null,null);HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();HttpPost httpPost = new HttpPost(REPORT_URL);RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();httpPost.setConfig(requestConfig);StringEntity postEntity = new StringEntity(data, "UTF-8");httpPost.addHeader("Content-Type", "text/xml");httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT);httpPost.setEntity(postEntity);HttpResponse httpResponse = httpClient.execute(httpPost);HttpEntity httpEntity = httpResponse.getEntity();return EntityUtils.toString(httpEntity, "UTF-8");}}package com.mons.utils;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.HttpClient;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.methods.HttpPost;import org.apache.http.config.RegistryBuilder;import org.apache.http.conn.ConnectTimeoutException;import org.apache.http.conn.socket.ConnectionSocketFactory;import org.apache.http.conn.socket.PlainConnectionSocketFactory;import org.apache.http.conn.ssl.DefaultHostnameVerifier;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.impl.conn.BasicHttpClientConnectionManager;import org.apache.http.util.EntityUtils;import .ssl.KeyManagerFactory;import .ssl.SSLContext;import java.io.InputStream;import .SocketTimeoutException;import .UnknownHostException;import java.security.KeyStore;import java.security.SecureRandom;public class WXPayRequest {privateWXPayConfig config;public WXPayRequest(WXPayConfig config) throws Exception{this.config = config;}/*** 请求,只请求一次,不做重试* @param domain* @param urlSuffix* @param uuid* @param data* @param connectTimeoutMs* @param readTimeoutMs* @param useCert 是否使用证书,针对退款、撤销等操作* @return* @throws Exception*/private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {BasicHttpClientConnectionManager connManager;if (useCert) {// 证书char[] password = config.getMchID().toCharArray();InputStream certStream = config.getCertStream();KeyStore ks = KeyStore.getInstance("PKCS12");ks.load(certStream, password);// 实例化密钥库 & 初始化密钥工厂KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());kmf.init(ks, password);// 创建 SSLContextSSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,new String[]{"TLSv1"},null,new DefaultHostnameVerifier());connManager = new BasicHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslConnectionSocketFactory).build(),null,null,null);}else {connManager = new BasicHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build(),null,null,null);}HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();String url = "https://" + domain + urlSuffix;HttpPost httpPost = new HttpPost(url);RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();httpPost.setConfig(requestConfig);StringEntity postEntity = new StringEntity(data, "UTF-8");httpPost.addHeader("Content-Type", "text/xml");httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + config.getMchID());httpPost.setEntity(postEntity);HttpResponse httpResponse = httpClient.execute(httpPost);HttpEntity httpEntity = httpResponse.getEntity();return EntityUtils.toString(httpEntity, "UTF-8");}private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {Exception exception = null;long elapsedTimeMillis = 0;long startTimestampMs =WXPayUtil.getCurrentTimestampMs();boolean firstHasDnsErr = false;boolean firstHasConnectTimeout = false;boolean firstHasReadTimeout = false;IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);if(domainInfo == null){throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");}try {String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);elapsedTimeMillis =WXPayUtil.getCurrentTimestampMs()-startTimestampMs;config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);WXPayReport.getInstance(config).report(uuid,elapsedTimeMillis,domainInfo.domain,domainInfo.primaryDomain,connectTimeoutMs,readTimeoutMs,firstHasDnsErr,firstHasConnectTimeout,firstHasReadTimeout);return result;}catch (UnknownHostException ex) {// dns 解析错误,或域名不存在exception = ex;firstHasDnsErr = true;elapsedTimeMillis =WXPayUtil.getCurrentTimestampMs()-startTimestampMs;WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);WXPayReport.getInstance(config).report(uuid,elapsedTimeMillis,domainInfo.domain,domainInfo.primaryDomain,connectTimeoutMs,readTimeoutMs,firstHasDnsErr,firstHasConnectTimeout,firstHasReadTimeout);}catch (ConnectTimeoutException ex) {exception = ex;firstHasConnectTimeout = true;elapsedTimeMillis =WXPayUtil.getCurrentTimestampMs()-startTimestampMs;WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);WXPayReport.getInstance(config).report(uuid,elapsedTimeMillis,domainInfo.domain,domainInfo.primaryDomain,connectTimeoutMs,readTimeoutMs,firstHasDnsErr,firstHasConnectTimeout,firstHasReadTimeout);}catch (SocketTimeoutException ex) {exception = ex;firstHasReadTimeout = true;elapsedTimeMillis =WXPayUtil.getCurrentTimestampMs()-startTimestampMs;WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);WXPayReport.getInstance(config).report(uuid,elapsedTimeMillis,domainInfo.domain,domainInfo.primaryDomain,connectTimeoutMs,readTimeoutMs,firstHasDnsErr,firstHasConnectTimeout,firstHasReadTimeout);}catch (Exception ex) {exception = ex;elapsedTimeMillis =WXPayUtil.getCurrentTimestampMs()-startTimestampMs;WXPayReport.getInstance(config).report(uuid,elapsedTimeMillis,domainInfo.domain,domainInfo.primaryDomain,connectTimeoutMs,readTimeoutMs,firstHasDnsErr,firstHasConnectTimeout,firstHasReadTimeout);}config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);throw exception;}/*** 可重试的,非双向认证的请求* @param urlSuffix* @param uuid* @param data* @return*/public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport);}/*** 可重试的,非双向认证的请求* @param urlSuffix* @param uuid* @param data* @param connectTimeoutMs* @param readTimeoutMs* @return*/public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);}/*** 可重试的,双向认证的请求* @param urlSuffix* @param uuid* @param data* @return*/public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport);}/*** 可重试的,双向认证的请求* @param urlSuffix* @param uuid* @param data* @param connectTimeoutMs* @param readTimeoutMs* @return*/public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);}}package com.mons.utils;import org.bouncycastle.jce.provider.BouncyCastleProvider;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import javax.crypto.Cipher;import javax.crypto.Mac;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;import javax.xml.parsers.DocumentBuilder;import javax.xml.transform.OutputKeys;import javax.xml.transform.Transformer;import javax.xml.transform.TransformerFactory;import javax.xml.transform.dom.DOMSource;import javax.xml.transform.stream.StreamResult;import java.io.ByteArrayInputStream;import java.io.InputStream;import java.io.StringWriter;import java.security.MessageDigest;import java.security.SecureRandom;import java.security.Security;import java.util.*;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 = 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();}}package com.mons.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;/*** /7/3*/public final class WXPayXmlUtil {public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();documentBuilderFactory.setFeature("/xml/features/disallow-doctype-decl", true);documentBuilderFactory.setFeature("/sax/features/external-general-entities", false);documentBuilderFactory.setFeature("/sax/features/external-parameter-entities", false);documentBuilderFactory.setFeature("/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();}}package com.mons.utils;import java.io.InputStream;public abstract class WXPayConfig {/*** 获取 App ID** @return App ID*/abstract String getAppID();/*** 获取 Mch ID** @return Mch ID*/abstract String getMchID();/*** 获取 API 密钥** @return API密钥*/abstract String getKey();/*** 获取商户证书内容** @return 商户证书内容*/abstract InputStream getCertStream();/*** HTTP(S) 连接超时时间,单位毫秒** @return*/public int getHttpConnectTimeoutMs() {return 6*1000;}/*** HTTP(S) 读数据超时时间,单位毫秒** @return*/public int getHttpReadTimeoutMs() {return 8*1000;}/*** 获取WXPayDomain, 用于多域名容灾自动切换* @return*/abstract IWXPayDomain getWXPayDomain();/*** 是否自动上报。* 若要关闭自动上报,子类中实现该函数返回 false 即可。** @return*/public boolean shouldAutoReport() {return true;}/*** 进行健康上报的线程的数量** @return*/public int getReportWorkerNum() {return 6;}/*** 健康上报缓存消息的最大数量。会有线程去独立上报* 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受** @return*/public int getReportQueueMaxSize() {return 10000;}/*** 批量上报,一次最多上报多个数据** @return*/public int getReportBatchSize() {return 10;}}

继承 WXPayConfing 配置 支付信息

package com.mons.utils;import com.itsoft.framework.core.utils.StringUtils;import org.springframework.stereotype.Service;import java.io.ByteArrayInputStream;import java.io.File;import java.io.FileInputStream;import java.io.InputStream;/*** 继承微信sdk的配置类*/@Servicepublic class MyWxPayConfig extends WXPayConfig {private byte[] certData;private String appId /*= "wx103babc6c843da68"*/;//微信提供的appidprivate String wxPayKey /*= "WUDHSBXN67648374834783wewuewuewe"*/;//微信提供的密钥private String wxPayMchId /*= "1527302381"*/;//微信提供的商务idpublic byte[] getCertData() {return certData;}public void setCertData(byte[] certData) {this.certData = certData;}public String getAppId() {return appId;}public void setAppId(String appId) {this.appId = appId;}public String getWxPayKey() {return wxPayKey;}public void setWxPayKey(String wxPayKey) {this.wxPayKey = wxPayKey;}public String getWxPayMchId() {return wxPayMchId;}public void setWxPayMchId(String wxPayMchId) {this.wxPayMchId = wxPayMchId;}//本地证书路径private String linuxOS /*= "/mnt/mall-service/pay/wechatKey/apiclient_cert.p12"*/;// private String windowsOS = "D:\\work\\mj_mall\\jeecg-cloud\\key\\apiclient_cert.p12";public String getLinuxOS() {return linuxOS;}public void setLinuxOS(String linuxOS) {this.linuxOS = linuxOS;}@Overridepublic String getAppID() {return appId;}@Overridepublic String getMchID() {return wxPayMchId;}@Overridepublic String getKey() {return wxPayKey;}@Overridepublic InputStream getCertStream() {return new ByteArrayInputStream(this.certData);}@Overridepublic IWXPayDomain getWXPayDomain() {IWXPayDomain iwxPayDomain = new IWXPayDomain() {@Overridepublic void report(String domain, long elapsedTimeMillis, Exception ex) {}@Overridepublic DomainInfo getDomain(WXPayConfig config) {return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);}};return iwxPayDomain;}/*** 构造方法读取证书, 通过getCertStream 可以使sdk获取到证书*/public void MyWxPayConfigInit() throws Exception {if (StringUtils.isNotBlank(getLinuxOS())){//通过服务操作系统判断使用的证书地址,win 和 linux 路径不通String property = System.getProperty("os.name");File file = null;if (property.contains("Windows") || property.contains("windows")) {//file = new File(windowsOS);file = new File(getLinuxOS());} else {file = new File(getLinuxOS());}InputStream certStream = new FileInputStream(file);this.certData = new byte[(int) file.length()];certStream.read(this.certData);certStream.close();}}}

MD5工具

package com.mons.utils;import java.security.MessageDigest;public class MD5Util {public final static String MD5(String s) {char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};try {byte[] btInput = s.getBytes();// 获得MD5摘要算法的 MessageDigest 对象MessageDigest mdInst = MessageDigest.getInstance("MD5");// 使用指定的字节更新摘要mdInst.update(btInput);// 获得密文byte[] md = mdInst.digest();// 把密文转换成十六进制的字符串形式int j = md.length;char str[] = new char[j * 2];int k = 0;for (int i = 0; i < j; i++) {byte byte0 = md[i];str[k++] = hexDigits[byte0 >>> 4 & 0xf];str[k++] = hexDigits[byte0 & 0xf];}return new String(str);} catch (Exception e) {e.printStackTrace();return null;}}private static String byteArrayToHexString(byte b[]) {StringBuffer resultSb = new StringBuffer();for (int i = 0; i < b.length; i++)resultSb.append(byteToHexString(b[i]));return resultSb.toString();}private static String byteToHexString(byte b) {int n = b;if (n < 0)n += 256;int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}public static String MD5Encode(String origin, String charsetname) {String resultString = null;try {resultString = new String(origin);MessageDigest md = MessageDigest.getInstance("MD5");if (charsetname == null || "".equals(charsetname))resultString = byteArrayToHexString(md.digest(resultString.getBytes()));elseresultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));} catch (Exception exception) {}return resultString;}private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};}

下单,退款 控制层

package com.itsoft.hotel.controller;import com.itsoft.framework.core.data.Result;import com.itsoft.hotel.entity.ManagerHotelInfoVO;import com.itsoft.hotel.entity.ManagerHotelOrderVO;import com.itsoft.hotel.service.ManagerHotelInfoService;import com.itsoft.hotel.service.ManagerHotelOrderService;import com.itsoft.hotel.service.PayService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;/*** 支付回调*/@RestController@RequestMapping(value = "/payRefund", produces = "application/json;charset=UTF-8")//@ResponseResult@Api(value = "支付回调", tags = ":支付回调")public class PayRefundController extends CommonController{@Autowiredprivate PayService payService;@Autowiredprivate ManagerHotelInfoService managerHotelInfoService;@Autowiredprivate ManagerHotelOrderService managerHotelOrderService;/*** 微信退款* @return*/@ApiOperation(notes = "微信退款", value = "微信退款")@ApiImplicitParam(name = "orderId", required = true, value = "订单id", dataType = "String")@RequestMapping(value = "/refund",method = RequestMethod.POST)public Result refund(String orderId) throws Exception {return payService.refund(orderId);}/*** 微信退款* @return*/@ApiOperation(notes = "微信支付", value = "微信支付")@ApiImplicitParams({@ApiImplicitParam(name = "orderId", required = true, value = "订单id", dataType = "String"),@ApiImplicitParam(name = "payType", required = true, value = "支付类型 NATIVE 扫码 JSAPI 公众号", dataType = "String"),@ApiImplicitParam(name = "openid", required = true, value = "openid", dataType = "String"),})@RequestMapping(value = "/pay",method = RequestMethod.POST)public Result pay(String orderId,String payType,String openid) throws Exception {//订单信息ManagerHotelOrderVO hotelOrderVO= managerHotelOrderService.getById(orderId);if (hotelOrderVO.getStatus()!=0) return Result.error("订单状态错误!");hotelOrderVO.setClientIp(getIp2(request));//酒店配置支付信息ManagerHotelInfoVO hotelInfoVO= managerHotelInfoService.getById(hotelOrderVO.getHotelId());return payService.pay(hotelOrderVO,hotelInfoVO,payType,openid);}}

service层

package com.itsoft.hotel.service;import com.itsoft.framework.core.data.DataMap;import com.itsoft.framework.core.data.Result;import com.itsoft.hotel.entity.ManagerHotelInfoVO;import com.itsoft.hotel.entity.ManagerHotelOrderVO;import com.itsoft.hotel.entity.PaySettingVO;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public interface PayService {/**** @param managerHotelOrderVO 订单号* @param mchInfo 商户信息* @param payType 支付类型 NATIVE 扫码 JSAPI 公众号* @return*/Result pay(ManagerHotelOrderVO managerHotelOrderVO, ManagerHotelInfoVO mchInfo, String payType, String openid);/*** wx回调* @param request* @return*/String wxNotifyuUrl(HttpServletRequest request, HttpServletResponse response);Result refund(String orderId) throws Exception;String wxRefundNotifyuUrl(HttpServletRequest request, HttpServletResponse response);}

serviceImpl 层

package com.itsoft.hotel.service.impl;import cn.hutool.core.collection.CollUtil;import com.alibaba.fastjson.JSONObject;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.itsoft.framework.core.data.Result;import com.itsoft.framework.core.logger.LoggerHelper;import com.itsoft.framework.core.utils.IdUtils;import com.mons.utils.*;import com.itsoft.hotel.entity.ManagerHotelInfoVO;import com.itsoft.hotel.entity.ManagerHotelOrderVO;import com.itsoft.hotel.mapper.ManagerHotelInfoMapper;import com.itsoft.hotel.mapper.ManagerHotelOrderMapper;import com.itsoft.hotel.service.ManagerHotelOrderService;import com.itsoft.hotel.service.PayService;import mons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;@Service@Transactional(propagation= Propagation.REQUIRED,rollbackFor = Exception.class)public class PayServiceImpl implements PayService {public static Logger log = LoggerFactory.getLogger(LoggerHelper.class);@Autowiredprivate MyWxPayConfig myWxPayConfig;@Autowiredprivate ManagerHotelOrderMapper managerHotelOrderMapper;@Autowiredprivate ManagerHotelInfoMapper managerHotelInfoMapper;@Autowiredprivate ManagerHotelOrderService managerHotelOrderService;/**** @param managerHotelOrderVO 订单号* @param mchInfo 商户信息* @param payType 支付类型 NATIVE 扫码 JSAPI 公众号* @param* @return*/@Overridepublic Result pay(ManagerHotelOrderVO managerHotelOrderVO, ManagerHotelInfoVO mchInfo, String payType, String openid) {if (StringUtils.isNotBlank(openid))managerHotelOrderVO.setOpenid(openid);return wxPayV2SubmitOrder(managerHotelOrderVO,mchInfo,payType);}/*** @param request* @return*/@Overridepublic String wxNotifyuUrl(HttpServletRequest request, HttpServletResponse response) {try {InputStream is = request.getInputStream();//将InputStream转换成StringBufferedReader reader = new BufferedReader(new InputStreamReader(is));StringBuilder sb = new StringBuilder();String line = null;try {while ((line = reader.readLine()) != null) {sb.append(line + "\n");}} catch (IOException e) {e.printStackTrace();} finally {try {is.close();} catch (IOException e) {e.printStackTrace();}}String resXml = sb.toString();log.info("微信支付异步通知请求包------");log.info(resXml);return wechatPayBack(resXml);} catch (Exception e) {log.error("微信支付回调通知失败", e);String result = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";return result;}}/*** 退款* @param orderId* @return*/@Overridepublic Result refund(String orderId) throws Exception {//根据订单详情 获取酒店支付配置ManagerHotelOrderVO managerHotelOrderVO= managerHotelOrderMapper.selectById(orderId);//获取酒店QueryWrapper<ManagerHotelInfoVO> managerHotelInfoVOQueryWrapper =new QueryWrapper<>();managerHotelInfoVOQueryWrapper.eq("id",managerHotelOrderVO.getHotelId());ManagerHotelInfoVO mchInfo =managerHotelInfoMapper.selectOne(managerHotelInfoVOQueryWrapper);myWxPayConfig.setWxPayKey(mchInfo.getHotelKey());myWxPayConfig.setAppId(mchInfo.getAppId());myWxPayConfig.setWxPayMchId(mchInfo.getMchId());myWxPayConfig.setLinuxOS(mchInfo.getApiclientCert());myWxPayConfig.MyWxPayConfigInit();Map<String, String> map = new HashMap<>();map.put("appid", mchInfo.getAppId());map.put("mch_id", mchInfo.getMchId());map.put("out_trade_no", orderId);//商户订单号map.put("out_refund_no", IdUtils.fastSimpleUUID());//商户退款单号map.put("total_fee", OrderNoUtil.changeY2F(managerHotelOrderVO.getTotalAmount()));map.put("refund_fee", OrderNoUtil.changeY2F(managerHotelOrderVO.getTotalAmount()));//退款金额map.put("refund_fee_type", "CNY");//币制,固定CNY,国内map.put("notify_url", mchInfo.getRefundNotifyUrl());//微信官方提供的SDK,下载可以之间调用,不需要写APIWXPay wxpay = new WXPay(myWxPayConfig);//申请退款Map<String, String> refundMap = wxpay.refund(map);log.info("====微信退款参数====={}", JSONObject.toJSON(refundMap));String returnCode = refundMap.get("return_code");String errCodeDes = refundMap.get("err_code_des");String resultCode = refundMap.get("result_code");if (returnCode.equals("SUCCESS") && resultCode.equals("SUCCESS")) {return Result.success("申请退款成功");} else {//修改主貼狀態 0支付成功,1退款成功,2退款失败managerHotelOrderService.updateStatus(orderId,"2");return Result.error("申请退款失败");}}/*** 退款回调* @param request* @param response* @return*/@Overridepublic String wxRefundNotifyuUrl(HttpServletRequest request, HttpServletResponse response) {try {InputStream is = request.getInputStream();//将InputStream转换成StringBufferedReader reader = new BufferedReader(new InputStreamReader(is));StringBuilder sb = new StringBuilder();String line = null;try {while ((line = reader.readLine()) != null) {sb.append(line + "\n");}} catch (IOException e) {e.printStackTrace();} finally {try {is.close();} catch (IOException e) {e.printStackTrace();}}String resXml = sb.toString();log.info("微信退款异步通知请求包------");log.info(resXml);return wechatRefund(resXml);} catch (Exception e) {log.error("微信退款回调通知失败", e);String result = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";return result;}}/*** 退款回调* @param xmlStr* @return*/public String wechatRefund(String xmlStr) {String xmlBack = "";Map<String, String> notifyMap = null;String lockKey = "refund_back_";try {// 转换成map,微信官方提供的SDKnotifyMap = WXPayUtil.xmlToMap(xmlStr);//获取appid 根据appid获取 app秘钥// 微信支付API密钥设置路径:微信商户平台(pay.)-->账户设置-->API安全-->密钥设置QueryWrapper<ManagerHotelInfoVO> managerHotelInfoVOQueryWrapper =new QueryWrapper<>();managerHotelInfoVOQueryWrapper.eq("mchId",notifyMap.get("mch_id"));ManagerHotelInfoVO managerHotelInfoVO= managerHotelInfoMapper.selectOne(managerHotelInfoVOQueryWrapper);myWxPayConfig.setWxPayKey(managerHotelInfoVO.getHotelKey());myWxPayConfig.setAppId(managerHotelInfoVO.getAppId());myWxPayConfig.setWxPayMchId(managerHotelInfoVO.getMchId());myWxPayConfig.setLinuxOS(managerHotelInfoVO.getApiclientCert());myWxPayConfig.MyWxPayConfigInit();//解密String reqInfo = AESUtil.decryptData(notifyMap.get("req_info"),managerHotelInfoVO.getHotelKey());//返回的数据Map<String, String> map = WXPayUtil.xmlToMap(reqInfo);// 转换成mapif (null == map || map.size() == 0) {log.info("解密异常");return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml>";}//支付交易号String transactionId = map.get("transaction_id");//订单号sys/sysDepart/batchSyncSysDepartString outTradeNo = map.get("out_trade_no");//退款交易号String refundId = map.get("refund_id");//退款单号String outRefundNo = map.get("out_refund_no");//订单金额String totalFee = map.get("total_fee");String settlementTotalFee = map.get("settlement_total_fee");//申请金额String refundFee = map.get("refund_fee");//退款金额String settlementRefundFee = map.get("settlement_refund_fee");//退款状态String refundStatus = map.get("refund_status");//成功时间String successTime = map.get("success_time");String refundRecvAccout = map.get("refund_recv_accout");String refundAccount = map.get("refund_account");String refundRequestSource = map.get("refund_request_source");if (null == settlementRefundFee) {log.info("退款金额为空");return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml>";}if (null == outRefundNo) {log.info("商户退款单号为空");return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml>";}if (null == refundId) {log.info("微信退款单号为空");return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml>";}log.info("\n\t----------------------------------------------------------\n\t" +"订单退款成功" +"\n\t----------------------------------------------------------");/*** 处理订单状态**/managerHotelOrderService.updateStatus(outTradeNo,"1");return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";} catch (Exception e) {e.printStackTrace();log.info("退款回调失败");return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml>";}}/*** 回调* @param xmlStr* @return*/public String wechatPayBack(String xmlStr) {String xmlBack = "";Map<String, String> notifyMap = null;try {notifyMap = WXPayUtil.xmlToMap(xmlStr); // 转换成map// 微信支付API密钥设置路径:微信商户平台(pay.)-->账户设置-->API安全-->密钥设置QueryWrapper<ManagerHotelInfoVO> managerHotelInfoVOQueryWrapper =new QueryWrapper<>();managerHotelInfoVOQueryWrapper.eq("mchId",notifyMap.get("mch_id"));ManagerHotelInfoVO mchInfo= managerHotelInfoMapper.selectOne(managerHotelInfoVOQueryWrapper);myWxPayConfig.setWxPayKey(mchInfo.getHotelKey());myWxPayConfig.setAppId(mchInfo.getAppId());myWxPayConfig.setWxPayMchId(mchInfo.getMchId());WXPay wxpay = new WXPay(myWxPayConfig);if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {log.info("签名成功 \r\n" + JSONObject.toJSONString(notifyMap));// 签名正确// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功String returnCode = notifyMap.get("return_code").trim();//状态//系统支付订单编号String outTradeNo = notifyMap.get("out_trade_no").trim();//订单号if (null == outTradeNo) {log.info("微信支付回调失败订单号: {}", notifyMap);xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg></xml> ";return xmlBack;}//交易类型String tradeType = notifyMap.get("trade_type").trim();//付款银行String bankType = notifyMap.get("bank_type").trim();//微信支付订单号String transactionId = notifyMap.get("transaction_id").trim();//支付金额String cashFee = notifyMap.get("cash_fee").trim();//订单金额String totalFee = notifyMap.get("total_fee").trim();//支付时间String timeEnd = notifyMap.get("time_end").trim();//是否关注公众账号String isSubscribe = notifyMap.get("is_subscribe").trim();SimpleDateFormat format = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss");Date date = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss").parse(format.format(new SimpleDateFormat("yyyyHHddHHmmss").parse(timeEnd)));log.info("微信支付回调成功订单号: {}", notifyMap);/*** 处理订单状态*/managerHotelOrderService.updateStatus(outTradeNo,"0");xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";return xmlBack;} else {log.error("微信支付回调通知签名错误");xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";return xmlBack;}} catch (Exception e) {log.error("微信支付回调通知失败", e);xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";}return xmlBack;}/*** 统一下单* @param managerHotelOrderVO* @param mchInfo* @param payType* @return*/public Result wxPayV2SubmitOrder(ManagerHotelOrderVO managerHotelOrderVO,ManagerHotelInfoVO mchInfo, String payType) {try {myWxPayConfig.setWxPayKey(mchInfo.getHotelKey());myWxPayConfig.setAppId(mchInfo.getAppId());myWxPayConfig.setWxPayMchId(mchInfo.getMchId());// 统一下单,myWxPayConfig是继承SDK的那个类WXPay wxpay = new WXPay(myWxPayConfig);Map<String, String> data = new HashMap<String, String>();data.put("body", managerHotelOrderVO.getHotelRoomName());data.put("out_trade_no", managerHotelOrderVO.getId()); // 订单唯一编号, 不允许重复//data.put("total_fee", String.valueOf(order.getOrderPrice().intValue())); // 订单金额, 单位分data.put("total_fee", OrderNoUtil.changeY2F(managerHotelOrderVO.getTotalAmount())); // 订单金额, 单位分data.put("spbill_create_ip", managerHotelOrderVO.getClientIp()); // 下单ipif (StringUtils.equals(payType,"NATIVE")){//扫码data.put("trade_type", "NATIVE"); // 固定填写}if (StringUtils.equals(payType,"JSAPI")){//扫码data.put("trade_type", "JSAPI"); // 固定填写data.put("openid", managerHotelOrderVO.getOpenid()); // 微信公众号统一标示openid}data.put("notify_url", mchInfo.getNotifyUrl()); // 订单结果通知, 微信主动回调此接口//下单Map<String, String> response = wxpay.unifiedOrder(data);log.info("===微信唤醒成功==={}",JSONObject.toJSONString(response));String returnCode = response.get("return_code");String resultCode = response.get("result_code");String returnMsg = response.get("return_msg");String prepayId = response.get("prepay_id");if (CollUtil.isEmpty(response) || !returnCode.equals("SUCCESS") ||!resultCode.equals("SUCCESS") || !returnMsg.equals("OK") || null == prepayId) {//微信支付下单失败log.error("========微信支付======{}",response.get("err_code_des"));return Result.error(response.get("err_code_des"));}if (StringUtils.equals(payType,"NATIVE")){//扫码String codeUrl = response.get("code_url");return Result.success(codeUrl);}if (StringUtils.equals(payType,"JSAPI")){//扫码//下单成功去获取调起微信支付参数Map<String, String> map = wechatCreatePay(prepayId);log.info(JSONObject.toJSONString(map));if (CollUtil.isEmpty(map) || map.size() == 0) {return Result.error("支付调起参数异常");}return Result.success(map);}} catch (Exception e) {e.printStackTrace();log.error("========微信支付异常======{}",e);}return Result.error("微信唤醒失败");}/*** JSPA 公众号支付 解析参数* @param prepayId* @return*/public Map<String, String> wechatCreatePay(String prepayId) {try {Map<String, String> wxPayMap = new HashMap<String, String>();wxPayMap.put("appId", myWxPayConfig.getAppID());wxPayMap.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));wxPayMap.put("nonceStr", WXPayUtil.generateNonceStr());wxPayMap.put("package", "prepay_id=" + prepayId);wxPayMap.put("signType", "MD5");// 加密串中包括 appId timeStamp nonceStr package signType 5个参数, 通过sdk WXPayUtil类加密, 注意, 此处使用 MD5加密 方式String sign = WXPayUtil.generateSignature(wxPayMap, myWxPayConfig.getKey());// 返回给前端调起微信支付的必要参数Map<String, String> map = new HashMap<>();map.put("prepay_id", prepayId);map.put("paySign", sign);map.putAll(wxPayMap);return map;} catch (Exception e) {e.printStackTrace();return null;}}}

成功回调 控制层

package com.itsoft.hotel.controller;import com.alibaba.fastjson.JSONObject;import com.itsoft.framework.core.data.DataMap;import com.itsoft.hotel.service.PayService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/*** 支付回调*/@Controller@RequestMapping(value = "/frontendPayNotify")//@ResponseResult@Api(value = "支付回调", tags = ":支付回调")public class FrontendPayNotifyController extends CommonController{@Autowiredprivate PayService payService;/*** 微信回调通知* @return*/@ApiOperation(notes = "微信回调通知", value = "微信回调通知")@ApiImplicitParams({})@RequestMapping(value = "/wxNotifyuUrl",method = RequestMethod.POST)public String wxNotifyuUrl(HttpServletRequest request,HttpServletResponse response) throws IOException {logger.info("==========微信回调参数request.getInputStream()======={}", JSONObject.toJSONString(request.getInputStream()));return payService.wxNotifyuUrl(request,response);}/*** 微信退款回调通知* @return*/@ApiOperation(notes = "微信退款回调通知", value = "微信退款回调通知")@ApiImplicitParams({})@RequestMapping(value = "/wxRefundNotifyuUrl",method = RequestMethod.POST)public String wxRefundNotifyuUrl(HttpServletRequest request,HttpServletResponse response) throws IOException {logger.info("==========微信退款回调通知getInputStream()======={}",JSONObject.toJSONString(request.getInputStream()));return payService.wxRefundNotifyuUrl(request,response);}}

pom文件

<dependency><groupId>org.jdom</groupId><artifactId>jdom2</artifactId><version>2.0.6</version></dependency><!-- /artifact/org.apache.httpcomponents/httpclient --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.3</version></dependency><!--httpUtil工具集成 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-http</artifactId><version>5.7.11</version></dependency>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。