Java SpringMVC實(shí)現(xiàn)PC端網(wǎng)頁微信掃碼支付(完整版)
一:前期微信支付掃盲知識(shí)
前提條件是已經(jīng)有申請(qǐng)了微信支付功能的公眾號(hào),然后我們需要得到公眾號(hào)APPID和微信商戶號(hào),這個(gè)分別在微信公眾號(hào)和微信支付商家平臺(tái)上面可以發(fā)現(xiàn)。其實(shí)在你申請(qǐng)成功支付功能之后,微信會(huì)通過郵件把Mail轉(zhuǎn)給你的,有了這些信息之后,我們就可以去微信支付服務(wù)支持頁面:https://pay.weixin.qq.com/service_provider/index.shtml
打開這個(gè)頁面,點(diǎn)擊右上方的鏈接【開發(fā)文檔】會(huì)進(jìn)入到API文檔說明頁面,看起來如下
選擇紅色圓圈的掃碼支付就是我們要做接入方式,鼠標(biāo)移動(dòng)到上面會(huì)提示你去查看開發(fā)文檔,如果這個(gè)都不知道怎么查看,可以洗洗睡了,你真的不合適做程序員,地址如下:
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 在瀏覽器中打開之后會(huì)看到
我們重點(diǎn)要關(guān)注和閱讀的內(nèi)容我已經(jīng)用紅色橢圓標(biāo)注好了,首先閱讀【接口規(guī)則】里面的協(xié)議規(guī)范,開玩笑這個(gè)都不讀你就想做微信支付,這個(gè)就好比你要去泡妞,得先收集點(diǎn)基本背景信息,了解對(duì)方特點(diǎn),不然下面還怎么溝通。事實(shí)證明只有會(huì)泡妞得程序員才是好銷售。跑題了我們接下來要看一下【場(chǎng)景介紹】中的案例與規(guī)范,只看一下記得一定要微信支付的LOGO下載下來,是為了最后放到我們自己的掃碼支付網(wǎng)頁上,這樣看上去比較專業(yè)一點(diǎn)。之后重點(diǎn)關(guān)注【模式二】
我們這里就是要采用模式二的方式實(shí)現(xiàn)PC端頁面掃碼支付功能。
微信官方對(duì)模式二的解釋是這樣的“商戶后臺(tái)系統(tǒng)先調(diào)用微信支付的統(tǒng)一下單接口,微信后臺(tái)系統(tǒng)返回鏈接參數(shù)code_url,商戶后臺(tái)系統(tǒng)將code_url值生成二維碼圖片,用戶使用微信客戶端掃碼后發(fā)起支付。注意:code_url有效期為2小時(shí),過期后掃碼不能再發(fā)起支付”。看明白了吧就是我們首先要調(diào)用微信提供統(tǒng)一下單接口,得到一個(gè)關(guān)鍵信息code_url(至于這個(gè)code_url是什么鬼,我也不知道),然后我們通過自己的程序把這個(gè)URL生成一個(gè)二維碼,生成二維碼我這里用了Google的zxing庫。然后把這個(gè)二維碼顯示在你的PC端網(wǎng)頁上就行啦。這樣終端用戶一掃碼就支付啦,支付就完成啦,看到這里你肯定很激動(dòng),發(fā)現(xiàn)微信支付如此簡(jiǎn)單,等等還有個(gè)事情我們還不知道,客戶知道付錢了,我們服務(wù)器端還不知道呢,以微信開發(fā)人員的智商他們?cè)缇拖氲竭@個(gè)問題了,所以讓你在調(diào)用統(tǒng)一下單接口的時(shí)候其中有個(gè)必填的參數(shù)就是回調(diào)URL,就是如果客戶端付款成功之后微信會(huì)通過這個(gè)URL向我們自己的服務(wù)器提交一些數(shù)據(jù),然后我們后臺(tái)解析這些數(shù)據(jù),完成我們自己操作。這樣我們才知道客戶是否真的已經(jīng)通過微信付款了。這樣整個(gè)流程才結(jié)束,這個(gè)就是模式二。微信用一個(gè)時(shí)序圖示這樣表示這個(gè)過程的。
表達(dá)起來比較復(fù)雜,看上去比較吃力,總結(jié)一下其實(shí)我們服務(wù)器該做的事情就如下件:
1. 通過統(tǒng)一下單接口傳入正確的參數(shù)(當(dāng)然要包括我們的回調(diào)URL)與簽名驗(yàn)證,從返回?cái)?shù)據(jù)中得到code_url的對(duì)應(yīng)數(shù)據(jù)
2. 根據(jù)code_url的數(shù)據(jù)我們自己生成一個(gè)二維碼圖片,顯示在瀏覽器網(wǎng)頁上
3. 在回調(diào)的URL中添加我們自己業(yè)務(wù)邏輯處理。
至此掃盲結(jié)束了,你終于知道掃碼支付什么個(gè)什么樣的流程了,下面我們就一起來扒扒它的相關(guān)API使用,做好每步處理。
二:開發(fā)過程
在開發(fā)代碼之前,請(qǐng)先準(zhǔn)備幾件事情。
1. 添加ZXing的maven依賴
2. 添加jdom的maven依賴
3.下載Java版本SDK演示程序,地址在這里
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
我們需要MD5Util.java和XMLUtil.java兩個(gè)文件
4. 我們使用HttpClient版本是4.5.1,記得添加Maven依賴
上面準(zhǔn)備工作做好以后,繼續(xù)往下看:
首先我們要調(diào)用微信的統(tǒng)一下單接口,我們點(diǎn)擊【API列表】中的統(tǒng)一下單會(huì)看到這樣頁面:
以本人調(diào)用實(shí)際情況為例,如下的參數(shù)是必須要有的,為了大家的方便我已經(jīng)把它變成一個(gè)POJO的對(duì)象, 代碼如下:
public class UnifiedorderDto implements WeiXinConstants { private String appid; private String body; private String device_info; private String mch_id; private String nonce_str; private String notify_url; private String openId; private String out_trade_no; private String spbill_create_ip; private int total_fee; private String trade_type; private String product_id; private String sign; public UnifiedorderDto() { this.appid = APPID; this.mch_id = WXPAYMENTACCOUNT; this.device_info = DEVICE_INFO_WEB; this.notify_url = CALLBACK_URL; this.trade_type = TRADE_TYPE_NATIVE; } public String getAppid() { return appid; } public void setAppid(String appid) { this.appid = appid; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getDevice_info() { return device_info; } public void setDevice_info(String device_info) { this.device_info = device_info; } public String getMch_id() { return mch_id; } public void setMch_id(String mch_id) { this.mch_id = mch_id; } public String getNonce_str() { return nonce_str; } public void setNonce_str(String nonce_str) { this.nonce_str = nonce_str; } public String getNotify_url() { return notify_url; } public void setNotify_url(String notify_url) { this.notify_url = notify_url; } public String getOpenId() { return openId; } public void setOpenId(String openId) { this.openId = openId; } public String getOut_trade_no() { return out_trade_no; } public void setOut_trade_no(String out_trade_no) { this.out_trade_no = out_trade_no; } public String getSpbill_create_ip() { return spbill_create_ip; } public void setSpbill_create_ip(String spbill_create_ip) { this.spbill_create_ip = spbill_create_ip; } public int getTotal_fee() { return total_fee; } public void setTotal_fee(int total_fee) { this.total_fee = total_fee; } public String getTrade_type() { return trade_type; } public void setTrade_type(String trade_type) { this.trade_type = trade_type; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } public String getProduct_id() { return product_id; } public void setProduct_id(String product_id) { this.product_id = product_id; } public String generateXMLContent() { String xml = "<xml>" + "" + this.appid + "</appid>" + "" + this.body + "" + "<device_info>WEB</device_info>" + "<mch_id>" + this.mch_id + "</mch_id>" + "<nonce_str>" + this.nonce_str + "</nonce_str>" + "<notify_url>" + this.notify_url + "</notify_url>" + "<out_trade_no>" + this.out_trade_no + "</out_trade_no>" + "<product_id>" + this.product_id + "</product_id>" + "<spbill_create_ip>" + this.spbill_create_ip+ "</spbill_create_ip>" + "<total_fee>" + String.valueOf(this.total_fee) + "</total_fee>" + "<trade_type>" + this.trade_type + "</trade_type>" + "<sign>" + this.sign + "</sign>" + "</xml>"; return xml; } public String makeSign() { String content ="appid=" + this.appid + "&body=" + this.body + "&device_info=WEB" + "&mch_id=" + this.mch_id + "&nonce_str=" + this.nonce_str + "?ify_url=" + this.notify_url + "&out_trade_no=" + this.out_trade_no + "&product_id=" + this.product_id + "&spbill_create_ip=" + this.spbill_create_ip+ "&total_fee=" + String.valueOf(this.total_fee) + "&trade_type=" + this.trade_type; content = content + "&key=" + WeiXinConstants.MD5_API_KEY; String esignature = WeiXinPaymentUtil.MD5Encode(content, "utf-8"); return esignature.toUpperCase(); } }
其中各個(gè)成員變量的解釋可以參見【統(tǒng)一下單接口】的說明即可。
有這個(gè)之后我們就要要設(shè)置的內(nèi)容填寫進(jìn)去,去調(diào)用該接口得到返回?cái)?shù)據(jù),從中拿到code_url的數(shù)據(jù)然后據(jù)此生成一個(gè)二維圖片,把圖片的地址返回給PC端網(wǎng)頁,然后它就會(huì)顯示出來,這里要特別說明一下,我們自己PC端網(wǎng)頁在點(diǎn)擊微信支付的時(shí)候就會(huì)通過ajax方式調(diào)用我們自己后臺(tái)的SpringMVC Controller然后在Controller的對(duì)應(yīng)方法中通過HTTPClient完成對(duì)微信統(tǒng)一下單接口調(diào)用解析返回的XML數(shù)據(jù)得到code_url的值,生成二維碼之后返回給前臺(tái)網(wǎng)頁。Controller中實(shí)現(xiàn)的代碼如下:
Map<string,object> result=new HashMap<string,object>(); UnifiedorderDto dto = new UnifiedorderDto(); if(cash == null || "".equals(cash)) { result.put("error", "cash could not be zero"); return result; } int totalfee = 100*Integer.parseInt(cash); logger.info("total recharge cash : " + totalfee); dto.setProduct_id(String.valueOf(System.currentTimeMillis())); dto.setBody("repair"); dto.setNonce_str(String.valueOf(System.nanoTime())); LoginInfo loginInfo = LoginInfoUtil.getLoginInfo(); // 通過我們后臺(tái)訂單號(hào)+UUID為身份識(shí)別標(biāo)志 dto.setOut_trade_no("你的訂單號(hào)+關(guān)鍵信息,微信回調(diào)之后傳回,你可以驗(yàn)證"); dto.setTotal_fee(totalfee); dto.setSpbill_create_ip("127.0.0.1"); // generate signature dto.setSign(dto.makeSign()); logger.info("sign : " + dto.makeSign()); logger.info("xml content : " + dto.generateXMLContent()); try { HttpClient httpClient = HttpClientBuilder.create().build(); HttpPost post = new HttpPost(WeiXinConstants.UNIFIEDORDER_URL); post.addHeader("Content-Type", "text/xml; charset=UTF-8"); StringEntity xmlEntity = new StringEntity(dto.generateXMLContent(), ContentType.TEXT_XML); post.setEntity(xmlEntity); HttpResponse httpResponse = httpClient.execute(post); String responseXML = EntityUtils.toString(httpResponse.getEntity(), "UTF-8"); logger.info("response xml content : " + responseXML); // parse CODE_URL CONTENT Map<string, string=""> resultMap = (Map<string, string="">)XMLUtil.doXMLParse(responseXML); logger.info("response code_url : " + resultMap.get("code_url")); String codeurl = resultMap.get("code_url"); if(codeurl != null && !"".equals(codeurl)) { String imageurl = generateQrcode(codeurl); result.put("QRIMAGE", imageurl); } post.releaseConnection(); } catch(Exception e) { e.printStackTrace(); } result.put("success", "1"); return result;</string,></string,></string,object></string,object>
生成二維碼的代碼如下:
private String generateQrcode(String codeurl) { File foldler = new File(basePath + "qrcode"); if(!foldler.exists()) { foldler.mkdirs(); } String f_name = UUIDUtil.uuid() + ".png"; try { File f = new File(basePath + "qrcode", f_name); FileOutputStream fio = new FileOutputStream(f); MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); Map hints = new HashMap(); hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); //設(shè)置字符集編碼類型 BitMatrix bitMatrix = null; bitMatrix = multiFormatWriter.encode(codeurl, BarcodeFormat.QR_CODE, 300, 300,hints); BufferedImage image = toBufferedImage(bitMatrix); //輸出二維碼圖片流 ImageIO.write(image, "png", fio); return ("qrcode/" + f_name); } catch (Exception e1) { e1.printStackTrace(); return null; } }
此時(shí)如何客戶端微信掃碼之后,微信就會(huì)通過回調(diào)我們制定URL返回?cái)?shù)據(jù)給我們。在回調(diào)方法中完成我們自己的處理,這里要特別注意的是你的回調(diào)接口必須通過HTTP POST方法實(shí)現(xiàn),否則無法接受到XML數(shù)據(jù)。回調(diào)處理的代碼如下:
@RequestMapping(value = "/your_callback_url", method = RequestMethod.POST) @ResponseBody public void finishPayment(HttpServletRequest request, HttpServletResponse response) { try { logger.info("start to callback from weixin server: " + request.getRemoteHost()); Map<string, string=""> resultMap = new HashMap<string, string="">(); InputStream inputStream = request.getInputStream(); // 讀取輸入流 SAXBuilder saxBuilder= new SAXBuilder(); Document document = saxBuilder.build(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子節(jié)點(diǎn) 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 = XMLUtil.getChildrenText(children); } resultMap.put(k, v); } // 驗(yàn)證簽名?。?! /* String[] keys = resultMap.keySet().toArray(new String[0]); Arrays.sort(keys); String kvparams = ""; for(int i=0; i<keys.length; i++)="" {="" if(keys[i].equals("esign"))="" continue;="" }="" 簽名算法="" if(i="=" 0)="" kvparams="" +="(keys[i]" "=" + resultMap.get(keys[i])); } else { kvparams += (" &"="" keys[i]="" &key=" + WeiXinConstants.MD5_API_KEY; String md5esign = WeiXinPaymentUtil.MD5Encode(esign, " utf-8");="" if(!md5esign.equals(resultmap.get("sign")))="" return;="" }*="" 關(guān)閉流="" 釋放資源="" inputstream.close();="" inputstream="null;" string="" returncode="resultMap.get("return_code");" outtradeno="resultMap.get("out_trade_no");" 以分為單位="" int="" nfee="Integer.parseInt(resultMap.get("total_fee"));" logger.info("out="" trade="" no="" :="" outtradeno);="" logger.info("total_fee="" nfee);="" 業(yè)務(wù)處理流程="" if("success".equals(returncode))="" todo:="" your="" business="" process="" add="" here="" response.getwriter().print(xmlutil.getretresultxml(resultmap.get("return_code"),="" resultmap.get("return_code")));="" else="" resultmap.get("return_msg")));="" catch(ioexception="" ioe)="" ioe.printstacktrace();="" catch="" (jdomexception="" e1)="" e1.printstacktrace();="" }
微信官方j(luò)ava版demo用到的xmlutil和md5util的兩個(gè)類記得拿過來改一下,演示代碼可以在它的官方演示頁面找到,相關(guān)maven依賴如下:
<dependency> <groupid>jdom</groupid> jdom</artifactid> <version>1.1</version> </dependency> <dependency> <groupid>com.google.zxing</groupid> core</artifactid> <version>3.3.0</version> </dependency>
最后要特別注意的是關(guān)于簽名,簽名生成MD5的類我是從微信官網(wǎng)直接下載Java版Demo程序獲取的,建議你也是,因?yàn)檫@個(gè)是確保MD5簽名是一致的最佳選擇。具體的生成簽名的算法可以查看微信官方文檔,這里也強(qiáng)烈建議大家一定要官方API說明,你開發(fā)中所遇到各種問題90%都是因?yàn)椴豢垂俜轿臋n,而是輕信某人博客!這個(gè)才是我寫這篇文章的真正目的和用意,根據(jù)官方文檔,用我的Java代碼實(shí)現(xiàn),微信PC端網(wǎng)頁掃碼支付必定在你的WEB應(yīng)用中飛起來。
以上所述是小編給大家介紹的Java SpringMVC實(shí)現(xiàn)PC端網(wǎng)頁微信掃碼支付(完整版),希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
并發(fā)編程之Java內(nèi)存模型volatile的內(nèi)存語義
這篇文章主要介紹了并發(fā)編程之Java內(nèi)存模型volatile的內(nèi)存語義,理解volatile特性的一個(gè)好辦法是把對(duì)volatile變量的單個(gè)讀/寫,看成是使用同一個(gè)鎖對(duì)單個(gè)讀/寫操作做了同步。下面我們一起進(jìn)入文章看看具體例子吧,需要的小伙伴可以參考下2021-11-11java雙向循環(huán)鏈表的實(shí)現(xiàn)代碼
這篇文章介紹了java雙向循環(huán)鏈表的實(shí)現(xiàn)代碼,有需要的朋友可以參考一下2013-09-09mybatis中數(shù)據(jù)加密與解密的實(shí)現(xiàn)
數(shù)據(jù)加解密的實(shí)現(xiàn)方式多種多樣,在mybatis環(huán)境中數(shù)據(jù)加解密變得非常簡(jiǎn)單易用,本文主要介紹了mybatis中數(shù)據(jù)加密與解密的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03