微信公衆號支付備忘及填坑之路-java

 

1、背景  php

  最近公司給第三方開發了一個公衆號,其中最重要的功能是支付,因爲是第一次開發,遇到的坑特別的多,截止我寫博客時,支付已經完成,在這裏我把遇到的坑記錄一下(不涉及退款)。不得不吐槽一下,騰訊這麼大的公司,寫的代碼真的爛,變量命名不規範(後來又開發了公衆號分享,發現對於同一個變量,兩個地方的變量名不一樣),文檔寫的垃圾,並且好多時候,有返回的錯誤碼可是文檔上沒說明,另外線上線下測試極其不方便。html

 

2、參考資料java

  一、JSAPI支付開發文檔:主要是支付開發過程當中接口、流程、注意事項、常見問題等;  node

  二、微信公衆平臺:裏面有公衆號的運維配置,固然也有開發配置,我們主要是用到了開發配置;  git

  三、微信商戶平臺:要支付必須有微信商戶,裏面需配置開發設置;  數據庫

  四、花生殼:主要是用到花生殼的內網穿透,因爲微信支付開發過程當中,須要公網的微信服務器訪問本地開發的電腦,包括重定向之類的,因此須要內網穿透,這樣微信才能訪問到局域網電腦;花生殼能夠免費註冊使用,可是呢,免費的帶寬爲1M,因爲帶寬過小,根本穿透不進來,因此咱們後來購買了帶寬,發現帶寬爲3M時,足以應付日場使用。固然Ngrok也能夠內網穿透,可是帶寬過低了;  後端

  五、微信公衆平臺支付接口調試工具:這個平臺能夠校驗本身生成的sign與微信服務器生成的sign是否一致,頗有用,尤爲是提示簽名失敗時。api

 

3、說明  數組

  一、在支付接入過程當中,若是不順利,不要懷疑微信服務器有問題,多想一想本身哪兒可能出問題了,由於接入微信支付的商戶已經達好幾億了,微信不可能出問題的;瀏覽器

  二、微信的開發文檔中說到,爲保證商戶接入質量,提高交易安全及用戶體驗,微信作了一個支付仿真沙箱環境,還說什麼「微信支付的合做服務商在正式上線交易前,必須先根據本文指引完成驗收」。這個別聽微信的,他們的這個沙箱環境根本不成熟,用這個是瞎折騰,我們能夠直接用真實環境測試;

  三、微信支付開發文檔中提到「協議規則」,首先傳輸方式是HTTPS,是指微信服務器和商戶服務器通信協議是HTTPS,可是不要求微信客戶端與商戶服務器之間的通信協議是HTTPS,用HTTP也行,也就是說商戶服務器不用本身準備SSL證書。其次全部微信支付有關的接口都用POST。最後,提交和返回的數據都是XML格式,不是目前普標接受的JSON格式,這個要注意。 

 

4、微信支付流程

  第3步:微信客戶端請求商戶後臺系統,主要是攜帶商品的價格,其次若有必要攜帶商品的描述,或者傳遞一個參數可讓服務器惟一肯定商品價格與商品描述;

  第4步:這一步是在咱們商戶後臺的數據庫的"訂單表"裏面插入一條數據,數據裏包含的基本信息有:用戶id,訂單金額,訂單商品及描述,支付狀態;

  第5步:整個微信公衆號支付流程圖中,最重要的一步,這一步的目的是返回預支付訂單號prepay_id,這一步起到承上啓下的做用;這一步的詳細代碼見後文 「統一下單」;開發文檔 ;

  第6步:根據第5步造成的prepay_id附帶其餘參數一塊兒發送給微信客戶端,開發文檔

  第7步:攜帶第6步的參數請求微信,這一步也是次重要的一步,開發文檔常見問題

  第10步、第11步:支付完成後,微信服務器通知商戶後臺系統,後臺系統驗證完畢後,須要改變商戶訂單表裏訂單的狀態,即第4步的訂單狀態。以後須要後臺系統給微信服務器返回特定的信息。可是開發文檔中也說了,絕大部分狀況下,咱們支付成功,能接收到微信的通知,可是有時收不到,這時咱們須要主動調用微信支付【查詢訂單API】確認訂單狀態 。這一步的詳細代碼見後文 「微信支付結果通知及商戶服務器響應」,開發文檔 。

 

 

5、微信公衆號開通微信支付

  見參考文檔:微信公衆號怎麼申請微信支付。開通以後,從公衆號內能夠看到已經關聯的商戶(圖1),同時從微信商戶裏能夠看到關聯的微信公衆號(圖2)。 

 

6、公衆號開發參數配置

  一、設置——公衆號設置——功能設置:設置網頁受權域名

  微信是這麼說的「用戶在網頁受權頁贊成受權給公衆號後,微信會將受權數據傳給一個回調頁面,回調頁面需在此域名下,以確保安全可靠」,我是這裏理解的:網頁須要重定向時才能夠跳轉到該域名下的地址。因爲是在微信裏面嵌套h5網頁,因此網頁的域名須要受到微信的監管。注意這裏的設置的是域名,不是ip地址之類的,也不能帶有端口號。這裏就須要用到花生殼的內網穿透了。

    假如服務器是tomcat,端口號是8080,微信給的文件是xxx.txt,花生殼地址是http://123asd.zicp.vip

    1)首先你將微信給的xxx.txt文件下載到ROOT目錄,而後啓動tomcat,在本地瀏覽器用localhost:8080/xxx.txt訪問,若是正常不能正常訪問,那就是tomcat啓動失敗了,本身檢查下log日誌看看;

    2)啓動花生殼客戶端,而且配置內網主機爲localhost:8080,在本地瀏覽器訪問http://123asd.zicp.vip/xxx.txt,這個受制於帶寬的緣由,可能須要等一會時間,也可能很快,若是不能訪問成功,花生殼重啓試試;

    3)配置網頁受權域名爲123asd.zicp.vip。

    

 

二、開發——基本配置。

  1)這裏須要配置開發者密碼(特別重要)、ip白名單(經過開發者ID及密碼調用獲取access_token接口時,須要設置訪問來源IP爲白名單)。

  2)服務器配置,驗證token。這一步也是微信防止沒有通過鑑權的服務器訪問本身的服務器作的一個驗證吧,能夠看作是爲了安全考慮。這個須要服務端代碼支持,代碼見後面。這個配置有時候須要多試幾回,有時提示「系統繁忙」。

 

7、微信商戶開發參數配置

  這裏的支付受權目錄配置的就是微信公衆號調起支付頁面的URL,和後端服務器無關。這裏有坑,詳見下面的填坑之路。

 

8、後端代碼

  一、微信常量類

 1 public class WeixinConstant {
 2 
 3     /**
 4      * 公衆號-id
 5      */
 6     public static final String WX_CON_APPID = "xxx";
 7     /**
 8      * 公衆號-祕鑰
 9      */
10     public static final String WX_CON_APPSECRET = "yyy";
11 
12 
13     /**
14      * 商戶-商戶號
15      */
16     public static final String WX_CON_MCH_ID = "zzz";
17     /**
18      * 商戶-商戶密鑰
19      */
20     public static final String WX_CON_MCHSECRET = "uuu";
21 
22 
23     /**
24      * 請求結果-成功
25      */
26     public static final String RESULT_SUCCESS = "SUCCESS";
27     /**
28      * 請求結果-失敗
29      */
30     public static final String RESULT_FAIL = "FAIL";
31 
32 
33     /**
34      * 微信支付成功回調響應Map
35      */
36     public static final Map<String, String> WX_CON_PAY_RESPMAP = new HashMap<String, String>() {{
37         put("return_code", "SUCCESS");
38         put("return_msg", "OK");
39     }};
40 
41 
42     /**
43      * 接口-用戶-經過code換取網頁受權access_token URL
44      */
45     public static final String WX_URL_USER_GETOPENIDANDTOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
46 
47     /**
48      * 接口-用戶-拉取用戶信息
49      */
50     public static final String WX_URL_USER_GETUSERINFO = "https://api.weixin.qq.com/sns/userinfo";
51 
52     /**
53      * 接口-支付-統一下單URL
54      */
55     public static final String WX_URL_PAY_UNIFIEDORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
56 
57     /**
58      * 接口-支付-查詢訂單URL
59      */
60     public static final String WX_URL_PAY_ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
61 
62 }
View Code

  二、工具類/方法

  2.一、map與xml轉化工具(微信工具類提供的。由於咱們熟悉使用map,因此咱們把請求參數放在有序map裏,而後再將map轉化爲xml)

  1 public class WXPayUtil {
  2 
  3     private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  4 
  5     private static final Random RANDOM = new SecureRandom();
  6 
  7     /**
  8      * XML格式字符串轉換爲Map
  9      *
 10      * @param strXML XML字符串
 11      * @return XML數據轉換後的Map
 12      * @throws Exception
 13      */
 14     public static Map<String, String> xmlToMap(String strXML) throws Exception {
 15         try {
 16             Map<String, String> data = new HashMap<String, String>();
 17             DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
 18             InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
 19             org.w3c.dom.Document doc = documentBuilder.parse(stream);
 20             doc.getDocumentElement().normalize();
 21             NodeList nodeList = doc.getDocumentElement().getChildNodes();
 22             for (int idx = 0; idx < nodeList.getLength(); ++idx) {
 23                 Node node = nodeList.item(idx);
 24                 if (node.getNodeType() == Node.ELEMENT_NODE) {
 25                     org.w3c.dom.Element element = (org.w3c.dom.Element) node;
 26                     data.put(element.getNodeName(), element.getTextContent());
 27                 }
 28             }
 29             try {
 30                 stream.close();
 31             } catch (Exception ex) {
 32                 // do nothing
 33             }
 34             return data;
 35         } catch (Exception ex) {
 36             WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
 37             throw ex;
 38         }
 39 
 40     }
 41 
 42     /**
 43      * 將Map轉換爲XML格式的字符串
 44      *
 45      * @param data Map類型數據
 46      * @return XML格式的字符串
 47      * @throws Exception
 48      */
 49     public static String mapToXml(Map<String, String> data) throws Exception {
 50         org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
 51         org.w3c.dom.Element root = document.createElement("xml");
 52         document.appendChild(root);
 53         for (String key: data.keySet()) {
 54             String value = data.get(key);
 55             if (value == null) {
 56                 value = "";
 57             }
 58             value = value.trim();
 59             org.w3c.dom.Element filed = document.createElement(key);
 60             filed.appendChild(document.createTextNode(value));
 61             root.appendChild(filed);
 62         }
 63         TransformerFactory tf = TransformerFactory.newInstance();
 64         Transformer transformer = tf.newTransformer();
 65         DOMSource source = new DOMSource(document);
 66         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
 67         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
 68         StringWriter writer = new StringWriter();
 69         StreamResult result = new StreamResult(writer);
 70         transformer.transform(source, result);
 71         String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
 72         try {
 73             writer.close();
 74         }
 75         catch (Exception ex) {
 76         }
 77         return output;
 78     }
 79 
 80 
 81     /**
 82      * 生成帶有 sign 的 XML 格式字符串
 83      *
 84      * @param data Map類型數據
 85      * @param key API密鑰
 86      * @return 含有sign字段的XML
 87      */
 88     public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
 89         return generateSignedXml(data, key, SignType.MD5);
 90     }
 91 
 92     /**
 93      * 生成帶有 sign 的 XML 格式字符串
 94      *
 95      * @param data Map類型數據
 96      * @param key API密鑰
 97      * @param signType 簽名類型
 98      * @return 含有sign字段的XML
 99      */
100     public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
101         String sign = generateSignature(data, key, signType);
102         data.put(WXPayConstants.FIELD_SIGN, sign);
103         return mapToXml(data);
104     }
105 
106 
107     /**
108      * 判斷簽名是否正確
109      *
110      * @param xmlStr XML格式數據
111      * @param key API密鑰
112      * @return 簽名是否正確
113      * @throws Exception
114      */
115     public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
116         Map<String, String> data = xmlToMap(xmlStr);
117         if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
118             return false;
119         }
120         String sign = data.get(WXPayConstants.FIELD_SIGN);
121         return generateSignature(data, key).equals(sign);
122     }
123 
124     /**
125      * 判斷簽名是否正確,必須包含sign字段,不然返回false。使用MD5簽名。
126      *
127      * @param data Map類型數據
128      * @param key API密鑰
129      * @return 簽名是否正確
130      * @throws Exception
131      */
132     public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
133         return isSignatureValid(data, key, SignType.MD5);
134     }
135 
136     /**
137      * 判斷簽名是否正確,必須包含sign字段,不然返回false。
138      *
139      * @param data Map類型數據
140      * @param key API密鑰
141      * @param signType 簽名方式
142      * @return 簽名是否正確
143      * @throws Exception
144      */
145     public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
146         if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
147             return false;
148         }
149         String sign = data.get(WXPayConstants.FIELD_SIGN);
150         return generateSignature(data, key, signType).equals(sign);
151     }
152 
153     /**
154      * 生成簽名
155      *
156      * @param data 待簽名數據
157      * @param key API密鑰
158      * @return 簽名
159      */
160     public static String generateSignature(final Map<String, String> data, String key) throws Exception {
161         return generateSignature(data, key, SignType.MD5);
162     }
163 
164     /**
165      * 生成簽名. 注意,若含有sign_type字段,必須和signType參數保持一致。
166      *
167      * @param data 待簽名數據
168      * @param key API密鑰
169      * @param signType 簽名方式
170      * @return 簽名
171      */
172     public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
173         Set<String> keySet = data.keySet();
174         String[] keyArray = keySet.toArray(new String[keySet.size()]);
175         Arrays.sort(keyArray);
176         StringBuilder sb = new StringBuilder();
177         for (String k : keyArray) {
178             if (k.equals(WXPayConstants.FIELD_SIGN)) {
179                 continue;
180             }
181             if (data.get(k).trim().length() > 0) // 參數值爲空,則不參與簽名
182                 sb.append(k).append("=").append(data.get(k).trim()).append("&");
183         }
184         sb.append("key=").append(key);
185         if (SignType.MD5.equals(signType)) {
186             return MD5(sb.toString()).toUpperCase();
187         }
188         else if (SignType.HMACSHA256.equals(signType)) {
189             return HMACSHA256(sb.toString(), key);
190         }
191         else {
192             throw new Exception(String.format("Invalid sign_type: %s", signType));
193         }
194     }
195 
196 
197     /**
198      * 獲取隨機字符串 Nonce Str
199      *
200      * @return String 隨機字符串
201      */
202     public static String generateNonceStr() {
203         char[] nonceChars = new char[32];
204         for (int index = 0; index < nonceChars.length; ++index) {
205             nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
206         }
207         return new String(nonceChars);
208     }
209 
210 
211     /**
212      * 生成 MD5
213      *
214      * @param data 待處理數據
215      * @return MD5結果
216      */
217     public static String MD5(String data) throws Exception {
218         MessageDigest md = MessageDigest.getInstance("MD5");
219         byte[] array = md.digest(data.getBytes("UTF-8"));
220         StringBuilder sb = new StringBuilder();
221         for (byte item : array) {
222             sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
223         }
224         return sb.toString().toUpperCase();
225     }
226 
227     /**
228      * 生成 HMACSHA256
229      * @param data 待處理數據
230      * @param key 密鑰
231      * @return 加密結果
232      * @throws Exception
233      */
234     public static String HMACSHA256(String data, String key) throws Exception {
235         Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
236         SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
237         sha256_HMAC.init(secret_key);
238         byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
239         StringBuilder sb = new StringBuilder();
240         for (byte item : array) {
241             sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
242         }
243         return sb.toString().toUpperCase();
244     }
245 
246     /**
247      * 日誌
248      * @return
249      */
250     public static Logger getLogger() {
251         Logger logger = LoggerFactory.getLogger("wxpay java sdk");
252         return logger;
253     }
254 
255     /**
256      * 獲取當前時間戳,單位秒
257      * @return
258      */
259     public static long getCurrentTimestamp() {
260         return System.currentTimeMillis()/1000;
261     }
262 
263     /**
264      * 獲取當前時間戳,單位毫秒
265      * @return
266      */
267     public static long getCurrentTimestampMs() {
268         return System.currentTimeMillis();
269     }
270 
271 }
View Code

  2.二、服務器token驗證工具

 1 public class CheckoutUtil {
 2 
 3 
 4     // 與接口配置信息中的Token要一致
 5     private static String token = "83cea0ca4c9ee2dd60c15f2df17de4a2";
 6 
 7     /**
 8      * 驗證簽名
 9      *
10      * @param signature
11      * @param timestamp
12      * @param nonce
13      * @return
14      */
15     public static boolean checkSignature(String signature, String timestamp, String nonce) {
16         String[] arr = new String[] { token, timestamp, nonce };
17         // 將token、timestamp、nonce三個參數進行字典序排序
18         // Arrays.sort(arr);
19         sort(arr);
20         StringBuilder content = new StringBuilder();
21         for (int i = 0; i < arr.length; i++) {
22             content.append(arr[i]);
23         }
24         MessageDigest md = null;
25         String tmpStr = null;
26 
27         try {
28             md = MessageDigest.getInstance("SHA-1");
29             // 將三個參數字符串拼接成一個字符串進行sha1加密
30             byte[] digest = md.digest(content.toString().getBytes());
31             tmpStr = byteToStr(digest);
32         } catch (NoSuchAlgorithmException e) {
33             e.printStackTrace();
34         }
35         content = null;
36         // 將sha1加密後的字符串可與signature對比,標識該請求來源於微信
37         return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
38     }
39 
40     /**
41      * 將字節數組轉換爲十六進制字符串
42      *
43      * @param byteArray
44      * @return
45      */
46     private static String byteToStr(byte[] byteArray) {
47         String strDigest = "";
48         for (int i = 0; i < byteArray.length; i++) {
49             strDigest += byteToHexStr(byteArray[i]);
50         }
51         return strDigest;
52     }
53 
54     /**
55      * 將字節轉換爲十六進制字符串
56      *
57      * @param mByte
58      * @return
59      */
60     private static String byteToHexStr(byte mByte) {
61         char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
62         char[] tempArr = new char[2];
63         tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
64         tempArr[1] = Digit[mByte & 0X0F];
65         String s = new String(tempArr);
66         return s;
67     }
68     public static void sort(String a[]) {
69         for (int i = 0; i < a.length - 1; i++) {
70             for (int j = i + 1; j < a.length; j++) {
71                 if (a[j].compareTo(a[i]) < 0) {
72                     String temp = a[i];
73                     a[i] = a[j];
74                     a[j] = temp;
75                 }
76             }
77         }
78     }
79 
80 }
View Code

  2.三、支付加簽工具

 1 public class WeixinSignUtil {
 2 
 3     private final static Log logger = LogFactory.getLog(WeixinSignUtil.class);
 4 
 5 
 6     /**
 7      * @param requestDataMap
 8      * @return
 9      * @function 微信參數簽名
10      * @remark flag=true 加密的參數爲sign  flag=false 加密的參數是paySign
11      */
12     public static void getWeixinSign(Map<String, String> requestDataMap, Boolean isRequestFlag) {
13         String paramStrTemp = "";
14         Iterator<Map.Entry<String, String>> entries = requestDataMap.entrySet().iterator();
15         while (entries.hasNext()) {
16             Map.Entry<String, String> entry = entries.next();
17             paramStrTemp += (entry.getKey() + "=" + entry.getValue() + "&");
18         }
19         String paramStr = paramStrTemp + "key=" + WeixinConstant.WX_CON_MCHSECRET;
20         logger.info("簽名前字符串:" + paramStr);
21         String sign = DigestUtils.md5DigestAsHex(paramStr.getBytes()).toUpperCase();
22         if (isRequestFlag) {
23             logger.info("加簽爲sign:" + sign);
24             requestDataMap.put("sign", sign);
25         } else {
26             logger.info("加簽爲paySign:" + sign);
27             requestDataMap.put("paySign", sign);
28         }
29     }
30 
31 }
View Code

  2.四、post方式提交xml數據工具(微信支付這樣要求的)

 1 public Map<String, String> wxPayRequest(LinkedHashMap<String, String> requestDataMap, String requestUrl) {
 2         Map<String, String> wxResponseMap;
 3         try {
 4             URLConnection con = new URL(requestUrl).openConnection();
 5             con.setDoOutput(true);
 6             con.setRequestProperty("Cache-Control", "no-cache");
 7             con.setRequestProperty("Content-Type", "text/xml");
 8 
 9             OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream());
10             out.write(new String(WXPayUtil.mapToXml(requestDataMap).getBytes("UTF-8")));
11             out.flush();
12             out.close();
13 
14             String line = "";
15             StringBuffer wxResponseStr = new StringBuffer();
16             BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
17             for (line = br.readLine(); line != null; line = br.readLine()) {
18                 wxResponseStr.append(line);
19             }
20 
21             logger.info("wxRespStr::" + wxResponseStr);
22             wxResponseMap = WXPayUtil.xmlToMap(wxResponseStr.toString());
23             logger.info("wxRespMap:" + wxResponseMap);
24             return wxResponseMap;
25         } catch (IOException e) {
26             e.printStackTrace();
27             return null;
28         } catch (Exception e) {
29             e.printStackTrace();
30             return null;
31         }
32     }
View Code

  三、服務器token驗證(配合上文的   6、公衆號開發參數配置——> 2)  

 1 @GetMapping("weixinVerifyUrl")
 2     @LoggerManage(logDescription = "系統-微信校驗URL的合法性")
 3     public void tokenVarify(HttpServletRequest request, HttpServletResponse response) {
 4 
 5         // 微信加密簽名
 6         String signature = request.getParameter("signature");
 7         // 時間戳
 8         String timestamp = request.getParameter("timestamp");
 9         // 隨機數
10         String nonce = request.getParameter("nonce");
11         // 隨機字符串
12         String echostr = request.getParameter("echostr");
13 
14         // 經過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,不然接入失敗
15         if (signature != null && CheckoutUtil.checkSignature(signature, timestamp, nonce)) {
16             try {
17                 response.getWriter().write(echostr);
18             } catch (IOException e) {
19                 e.printStackTrace();
20             }
21             return;
22         }
23         return;
24     }
View Code

  四、統一下單

 1 public Map<String, Object> weixinPay(DealRecord dealRecord, Byte memberGrade, String userId) {
 2         Map<String, Object> retMap = new HashMap<>(2);
 3 
 4         /**
 5          * 一、用戶背景校驗
 6          */
 7        
 8 
 9         /**
10          * 二、準備請求參數
11          */
12         LinkedHashMap<String, String> requestDataMap = new LinkedHashMap<>();
13         requestDataMap.put("appid", WeixinConstant.WX_CON_APPID);
14         requestDataMap.put("attach", memberGrade + "");
15         requestDataMap.put("body", dealRecord.getGoodsName() + "-" + memberGrade);
16         requestDataMap.put("device_info", "WEB");
17         requestDataMap.put("fee_type", "CNY");
18         requestDataMap.put("mch_id", WeixinConstant.WX_CON_MCH_ID);
19         requestDataMap.put("nonce_str", UUIDUtil.getUUID());
20         requestDataMap.put("notify_url", weixinPayNotifyUrl);
21         requestDataMap.put("openid", dealRecord.getOpenid());
22         requestDataMap.put("out_trade_no", dealRecord.getId());
23         requestDataMap.put("sign_type", "MD5");
24         requestDataMap.put("spbill_create_ip", dealRecord.getDeviceIp());
25         requestDataMap.put("time_start", DateUtil.formatDate(dealRecord.getCreateTime(), DateUtil.SDF_yyyyMMddHHmmss));
26         // 雖然文檔上total_fee爲int,可是String也能夠
27         requestDataMap.put("total_fee", dealRecord.getAmount() + "");
28         requestDataMap.put("trade_type", "JSAPI");
29 
30         /**
31          * 三、微信請求參數簽名+下單
32          */
33         WeixinSignUtil.getWeixinSign(requestDataMap, true);
34         Map<String, String> wxResponseMap = weixinService.wxPayRequest(requestDataMap, WeixinConstant.WX_URL_PAY_UNIFIEDORDER);
35 
36         /**
37          * 四、邏輯處理
38          */
39         String return_code = wxResponseMap.get("return_code");
40         if (WeixinConstant.RESULT_FAIL.equals(return_code)) {
41             retMap.put("result", 0);
42             retMap.put("data", wxResponseMap);
43             return retMap;
44         } else if (WeixinConstant.RESULT_SUCCESS.equals(return_code)) {
45             String prepay_id = wxResponseMap.get("prepay_id");
46 
47             LinkedHashMap<String, String> wxDataMap = new LinkedHashMap<>(6);
48             wxDataMap.put("appId", WeixinConstant.WX_CON_APPID);
49             wxDataMap.put("nonceStr", UUIDUtil.getUUID());
50             wxDataMap.put("package", "prepay_id=" + prepay_id);
51             wxDataMap.put("signType", "MD5");
52             wxDataMap.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
53 
54             // 微信請求參數簽名
55             WeixinSignUtil.getWeixinSign(wxDataMap, false);
56             
57             retMap.put("result", 0);
58             retMap.put("data", wxDataMap);
59             return retMap;
60         }
61         retMap.put("result", 9);
62         return retMap;
63     }
View Code

  五、微信支付結果通知及商戶服務器響應

 1 /**
 2      * @param request
 3      * @function 微信/支付通知
 4      */
 5     @RequestMapping(value = "/weixin/pay/notify")
 6     @LoggerManage(logDescription = "用戶-微信/支付/回調通知")
 7     public void weixinPayNotify(HttpServletRequest request, HttpServletResponse response) {
 8         BufferedReader reader = null;
 9         StringBuilder wxRequestStr = new StringBuilder();
10         Map<String, String> wxRequestMap = null;
11         try {
12             reader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
13             String line = null;
14             while ((line = reader.readLine()) != null) {
15                 wxRequestStr.append(line);
16             }
17 
18             logger.info("wxNotifyReqStr:" + wxRequestStr.toString());
19             wxRequestMap = WXPayUtil.xmlToMap(wxRequestStr.toString());
20             logger.info("wxNotifyReqMap:" + wxRequestMap);
21         } catch (IOException e) {
22             e.printStackTrace();
23         } catch (Exception e) {
24             e.printStackTrace();
25         } finally {
26             try {
27                 if (null != reader) {
28                     reader.close();
29                 }
30             } catch (IOException e) {
31             }
32         }
33 
34         String return_code = wxRequestMap.get("return_code");
35         if (WeixinConstant.RESULT_FAIL.equals(return_code)) {
36             // 支付失敗
37             logger.info("交易失敗!");
38         } else if (WeixinConstant.RESULT_SUCCESS.equals(return_code)) {
39             
40             try {
41                 BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
42                 out.write(WXPayUtil.mapToXml(WeixinConstant.WX_CON_PAY_RESPMAP).getBytes("UTF-8"));
43                 out.flush();
44                 out.close();
45             } catch (IOException e) {
46                 e.printStackTrace();
47             } catch (Exception e) {
48                 e.printStackTrace();
49             }
50         }
51     }
View Code

 

9、填坑之路

  一、統一下單接口,請求微信,提示簽名失敗

1)仔細檢查統一下單接口裏攜帶的參數,參數大小寫、參數是否必須攜帶。好比是appid而不是appId;openid文檔上寫的是「」,可是它在備註裏寫到trade_type=JSAPI時(即JSAPI支付),此參數必傳,這個注意啊;

2)加密方式是否正確,能夠把本身生成的sign與微信公衆平臺支付接口調試工具生成的sign作對比,若是不一致,那證實你的加簽方式不正確,請仔細閱讀加簽安全規範這篇文章,好比裏面提到的按照參數名ASCII字典序排序、拼接商戶密鑰、MD5加密並轉化爲大寫;

3)若是第二步沒問題,好吧,不要猶豫,去重置商戶祕鑰吧,注意是商戶祕鑰,由於支付加簽時和商戶祕鑰有關,我當時就是卡在這一步了很久,至今記憶深入。

  二、微信內h5調起支付,提示「支付驗證簽名失敗」

1)仔細檢查參數,參數大小寫、參數是否必須攜帶。好比是appId而不是appid(統一下單與這個正好相反);package的value必須是「prepay_id=xxx」形式;

2)統一下單的簽名的key是sign,而微信調起h5的簽名的key是paySign,這個要注意;

3)加密方式是否正確,能夠把本身生成的sign與微信公衆平臺支付接口調試工具生成的sign作對比,若是不一致,那證實你的加簽方式不正確,請仔細閱讀加簽安全規範這篇文章,好比裏面提到的按照參數名ASCII字典序排序、拼接商戶密鑰、MD5加密並轉化爲大寫;

4)若是第三步沒問題,好吧,不要猶豫,去重置商戶祕鑰吧。

  三、微信內h5調起支付,提示「當前頁面未註冊 http://xxx」

1)檢查微信商戶平臺–>產品中心–>開發配置–>支付受權目錄是否配置;

2)若是第一步沒問題,但仍是報錯,那就得注意配置了。好比你的支付頁是http://www.abc.com/pay/wxjspay/id/50.html,那配置爲http://www.abc.com/pay/wxjspay/id/;假如是http://www.abc.com/pay/wxjspay.php,那配置爲http://www.abc.com/pay/。規律就是去掉最後一個反斜槓的內容。

相關文章
相關標籤/搜索