這幾天一直在研究微信支付回調這個問題,發現之前微信支付回調都是正常的也沒怎么在意,今天在自己項目上測試的時候發現相同的代碼在我這個項目上微信支付回調老是重復執行導致支付成功之后的回調邏輯一直在執行,很頭疼。回調邏輯都在執行,說明回調正常執行
網上有些給的答案:
微信沒有正常接收到SUCCESS消息建議將resXml:
resXml ="<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
修改為:resXml = "SUCCESS";
也有人反映這個方法可行,但是我自己這邊沒能執行成功。
也有部分人說修改成
resXml ="<xml>" + "<return_code>SUCCESS</return_code>"+ "<return_msg>OK</return_msg>" + "</xml> ";
同樣部分人說可以 我修改了還是不行。
首先看下我的支付回調代碼(這邊只說微信支付回調,因為到這里應該都是微信支付通了的吧,我也有一篇是寫微信支付的,點擊這個鏈接)
/** * @author Mr.Lin * @description 微信支付回調 * @date 2019/7/5 */ @Service public class WeiXinPayNotifyServiceImpl implements WeiXinPayNotifyService { private final static Logger LOGGER = LoggerFactory.getLogger(WeiXinPayNotifyServiceImpl.class); @Override public void weixinpay_notify(HttpServletRequest request, HttpServletResponse response) {
/**
這些是通過微信支付,返回可以得到一些值 具體如下截圖 這個代碼可以不用管 下面會繼續給出成功且不重復回調的代碼 這邊的支付都是JSAPA支付
*/
InputStream inputStream; StringBuffer sb = new StringBuffer(); try { inputStream = request.getInputStream(); String s; BufferedReader in = new BufferedReader(new InputStreamReader( inputStream, "UTF-8")); while ((s = in.readLine()) != null) { sb.append(s); } in.close(); inputStream.close(); Map<String, String> m = new HashMap<String, String>(); m = WXUtil.doXMLParse(sb.toString()); SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>(); Iterator it = m.keySet().iterator(); while (it.hasNext()) { String parameter = (String) it.next(); String parameterValue = m.get(parameter); String v = ""; if (null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } String key = WxPayPojo.MCH_KEY; // 秘鑰 這個就是你的微信商戶平台的秘鑰 if (WXUtil.isTenpaySign("UTF-8", packageParams, key)) { String resXml = ""; if ("SUCCESS".equals((String) packageParams.get("result_code"))) { // 得到返回的參數 String openid = (String) packageParams.get("openid"); String transaction_id = (String) packageParams .get("transaction_id"); String orderNumberMain = (String) packageParams .get("out_trade_no"); // 這里可以寫你需要的業務 LOGGER.debug("我是回調函數啊!---我執行了---------------------"); LOGGER.debug("openid---->" + openid); LOGGER.debug("transaction_id---->" + transaction_id); LOGGER.debug("out_trade_no---->" + orderNumberMain); resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); return; } else { LOGGER.debug("回調失敗"); } } else { LOGGER.debug("回調失敗"); } } catch (IOException e) { e.printStackTrace(); } catch (JDOMException e) { e.printStackTrace(); } } }
下面是微信支付回調可以調用的參數,你看你項目會用到什么,一般來說獲取到訂單編號 通過訂單編號操作(點擊這里查看微信支付回調JSAPI文檔):
即上面的都可以通過packageParams.get(") 對應的到返回值。
下面是我參考一個博客之后修改的,符合我的這個要求:鏈接如下:https://blog.csdn.net/qq_37105358/article/details/81285779
/** * 微信小程序支付成功回調函數 * @param request * @param response * @throws Exception */ @RequestMapping(value = "/weixinpay/notify") public void wxNotify(HttpServletRequest request,HttpServletResponse response) throws Exception{ BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while((line = br.readLine()) != null){ sb.append(line); } br.close(); //sb為微信返回的xml String notityXml = sb.toString(); String resXml = ""; System.out.println("接收到的報文:" + notityXml); Map map = PayUtil.doXMLParse(notityXml); String returnCode = (String) map.get("return_code"); if("SUCCESS".equals(returnCode)){ //驗證簽名是否正確 Map<String, String> validParams = PayUtil.paraFilter(map); //回調驗簽時需要去除sign和空值參數 String validStr = PayUtil.createLinkString(validParams);//把數組所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串 String sign = PayUtil.sign(validStr, WxPayPojo.MCH_KEY, "utf-8").toUpperCase();//拼裝生成服務器端驗證的簽名 // 因為微信回調會有八次之多,所以當第一次回調成功了,那么我們就不再執行邏輯了 //根據微信官網的介紹,此處不僅對回調的參數進行驗簽,還需要對返回的金額與系統訂單的金額進行比對等 if(sign.equals(map.get("sign"))){ // 得到返回的參數 //這邊我上面也說過了 同理 需要什么參數 直接通過map.get獲取 參數列表我上面也列舉了 String openid = (String) map.get("openid"); String transaction_id = (String) map.get("transaction_id"); String orderNumberMain = (String) map.get("out_trade_no"); /**回調邏輯代碼編寫*/ //通知微信服務器已經支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { System.out.println("微信支付回調失敗!簽名不一致"); } }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> "; } System.out.println(resXml); System.out.println("微信支付回調數據結束"); BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); }
用到的工具類:
import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.security.SignatureException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; public class PayUtil { /** * * 簽名字符串 * * @param text 需要簽名的字符串 * * @param key 密鑰 * * @param input_charset 編碼格式 * * @return 簽名結果 * */ public static String sign(String text, String key, String input_charset) { text = text + "&key=" + key; return DigestUtils.md5Hex(getContentBytes(text, input_charset)); } /** * * 簽名字符串 * * @param text 需要簽名的字符串 * * @param sign 簽名結果 * * @param key 密鑰 * * @param input_charset 編碼格式 * * @return 簽名結果 * */ public static boolean verify(String text, String sign, String key, String input_charset) { text = text + key; String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)); if (mysign.equals(sign)) { return true; } else { return false; } } /** * * @param content * * @param charset * * @return * * @throws SignatureException * * @throws UnsupportedEncodingException * */ public static byte[] getContentBytes(String content, String charset) { if (charset == null || "".equals(charset)) { return content.getBytes(); } try { return content.getBytes(charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("MD5簽名過程中出現錯誤,指定的編碼集不對,您目前指定的編碼集是:" + charset); } } private static boolean isValidChar(char ch) { if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) return true; if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f)) return true;// 簡體中文漢字編碼 return false; } /** * * 除去數組中的空值和簽名參數 * * @param sArray 簽名參數組 * * @return 去掉空值與簽名參數后的新簽名參數組 * */ public static Map<String, String> paraFilter(Map<String, String> sArray) { Map<String, String> result = new HashMap<String, String>(); if (sArray == null || sArray.size() <= 0) { return result; } for (String key : sArray.keySet()) { String value = sArray.get(key); if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { continue; } result.put(key, value); } return result; } /** * * 把數組所有元素排序,並按照“參數=參數值”的模式用“&”字符拼接成字符串 * * @param params 需要排序並參與字符拼接的參數組 * * @return 拼接后字符串 * */ public static String createLinkString(Map<String, String> params) { List<String> keys = new ArrayList<String>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (i == keys.size() - 1) {// 拼接時,不包括最后一個&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } /** * * * * @param requestUrl 請求地址 * * @param requestMethod 請求方法 * * @param outputStr 參數 * */ public static String httpRequest(String requestUrl, String requestMethod, String outputStr) { // 創建SSLContext StringBuffer buffer = null; try { URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(requestMethod); conn.setDoOutput(true); conn.setDoInput(true); conn.connect(); //往服務器端寫內容 if (null != outputStr) { OutputStream os = conn.getOutputStream(); os.write(outputStr.getBytes("utf-8")); os.close(); } // 讀取服務器端返回的內容 InputStream is = conn.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "utf-8"); BufferedReader br = new BufferedReader(isr); buffer = new StringBuffer(); String line = null; while ((line = br.readLine()) != null) { buffer.append(line); } br.close(); } catch (Exception e) { e.printStackTrace(); } return buffer.toString(); } public static String urlEncodeUTF8(String source) { String result = source; try { result = java.net.URLEncoder.encode(source, "UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** * * 解析xml,返回第一級元素鍵值對。如果第一級元素有子節點,則此節點的值是子節點的xml數據。 * * @param strxml * * @return * * @throws JDOMException * * @throws IOException * */ public static Map doXMLParse(String strxml) throws Exception { if (null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = String2Inputstream(strxml); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //關閉流 in.close(); return m; } /** * * 獲取子結點的xml * * @param children * * @return String * */ public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } public static InputStream String2Inputstream(String str) { return new ByteArrayInputStream(str.getBytes()); } }
回調的鏈接 你微信支付的時候 應該也有寫到的 就跟前端正常調用接口一樣 比如:https://www.cnblogs.com/ 這個是你項目的域名 這個你項目名稱lxwt
回調鏈接就是:https://www.cnblogs.com/lxwt/weixinpay/notify 就可以了
我跟他這個代碼對比下發現其實都是差不多的 一個意思 就是不清楚為啥會重復回調 可能是我的工具類解析的問題吧 下面這個我測試過了 不會出現重復回調了
有更好的方法請留言 謝謝!