掃碼支付
本文附有代碼,在下方,如果不熟悉場景的可以看看下面的場景介紹
用戶掃描商戶展示在各種場景的二維碼進行支付。
步驟1:商戶根據微信支付的規則,為不同商品生成不同的二維碼(如圖6.1),展示在各種場景,用於用戶掃描購買。
步驟2:用戶使用微信“掃一掃”(如圖6.2)掃描二維碼后,獲取商品支付信息,引導用戶完成支付(如圖6.3)。
![]() 圖6.1 支付二維碼 |
![]() 圖6.2 打開微信掃一掃二維碼 |
![]() 圖6.3 確認支付頁面 |
步驟(3):用戶確認支付,輸入支付密碼(如圖6.4)。
步驟(4):支付完成后會提示用戶支付成功(如圖6.5),商戶后台得到支付成功的通知,然后進行發貨處理。
![]() 圖6.4 用戶確認支付,輸入密碼 |
![]() 圖6.5 支付成功提示 |
模式二與模式一相比,流程更為簡單,不依賴設置的回調支付URL。商戶后台系統先調用微信支付的統一下單接口,微信后台系統返回鏈接參數code_url,商戶后台系統將code_url值生成二維碼圖片,用戶使用微信客戶端掃碼后發起支付。注意:code_url有效期為2小時,過期后掃碼不能再發起支付。
業務流程時序圖
業務流程說明:
(1)商戶后台系統根據用戶選購的商品生成訂單。
(2)用戶確認支付后調用微信支付【統一下單API】生成預支付交易;
(3)微信支付系統收到請求后生成預支付交易單,並返回交易會話的二維碼鏈接code_url。
(4)商戶后台系統根據返回的code_url生成二維碼。
(5)用戶打開微信“掃一掃”掃描二維碼,微信客戶端將掃碼內容發送到微信支付系統。
(6)微信支付系統收到客戶端請求,驗證鏈接有效性后發起用戶支付,要求用戶授權。
(7)用戶在微信客戶端輸入密碼,確認支付后,微信客戶端提交授權。
(8)微信支付系統根據用戶授權完成支付交易。
(9)微信支付系統完成支付交易后給微信客戶端返回交易結果,並將交易結果通過短信、微信消息提示用戶。微信客戶端展示支付交易結果頁面。
(10)微信支付系統通過發送異步消息通知商戶后台系統支付結果。商戶后台系統需回復接收情況,通知微信后台系統不再發送該單的支付通知。
(11)未收到支付通知的情況,商戶后台系統調用【查詢訂單API】。
(12)商戶確認訂單已支付后給用戶發貨。
生成二維碼規則
對應鏈接格式:weixin://wxpay/bizpayurl?sr=XXXXX。請商戶調用第三方庫將code_url生成二維碼圖片。該模式鏈接較短,生成的二維碼打印到結賬小票上的識別率較高。
例如,將weixin://wxpay/s/An4baqw生成二維碼見圖6.10。
圖6.10 原生支付“模式二”二維碼示例
下面上代碼:
這里特要注意的就是key,這個key不是公眾號下面的key,而是商戶下的key,這個key是自己生成的32位字符串,
調用的是微信統一下單接口:
getWxPayQRCode() 返回的就是要掃的二維碼中的值;只要把這個值塞到二維中即可!
/** * 微信掃碼支付 模式二 * @return返回的就是二維中的值 * @throws Exception */ public String getWxPayQRCode() throws Exception{ //商戶key String key = "mlqho2dwhxxxxxxxxxxxv81m1r6i28"; SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); Map<String,String> map = new HashMap<String, String>(); //公眾賬號ID map.put("appid","wssssssxxxxxxxxxe0e5b"); //商戶號 map.put("mch_id","1xxxxxxxxx2"); //隨機數 map.put("nonce_str", RandomStringGenerator.getRandomStringByLength(32)); //商品描述 map.put("body","香辣雞腿堡"); //訂單號 map.put("out_trade_no",sdf.format(new Date())); //總金額單位分 map.put("total_fee","1"); InetAddress ia = InetAddress.getLocalHost(); //ip map.put("spbill_create_ip",ia.getHostAddress()); //通知地址 map.put("notify_url","www.niudao.com"); //交易類型 map.put("trade_type","NATIVE"); //商品ID map.put("product_id","10000"); //簽名 map.put("sign", QRSign.getSign(map, key)); //請求地址及請求數據 String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String data = MapToXmlUtil.mapToXml(map); //發送請求 ClientRequest request = new ClientRequest(url); request.body("text/xml;charset=utf-8",data); ClientResponse response = request.post(String.class); //xml字符串轉化成json對象 String result = XMLAndJsonUtil.xmlChangeJson(response.getEntity().toString()); result = result.replaceAll("\r\n",""); JSONObject jsonObject = JSONObject.fromObject(result); //二維碼內容 String qrCode = ""; if(jsonObject.get("return_code").toString().equals("SUCCESS")){ qrCode = jsonObject.get("code_url").toString(); } return qrCode; }
工具類
RandomStringGenerator 傳入一個n,生成n位隨機字符串
import java.util.Random; /** * User: rizenguo * Date: 2014/10/29 * Time: 14:18 */ public class RandomStringGenerator { /** * 獲取一定長度的隨機字符串 * @param length 指定字符串長度 * @return 一定長度的字符串 */ public static String getRandomStringByLength(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } }
QRSign 傳入的數據生成簽名,
import java.security.MessageDigest; import java.util.Arrays; import java.util.Map; /** * Created by Administrator on 16-12-1. */ public class QRSign { /** * 微信支付簽名算法sign * @param map 請求微支付body * @param key 商戶key (不是公眾號key) * @return */ public static String getSign(Map<String,String> map,String key) { StringBuffer sb = new StringBuffer(); String[] keyArr = (String[]) map.keySet().toArray(new String[map.keySet().size()]);//獲取map中的key轉為array Arrays.sort(keyArr);//對array排序 for (int i = 0, size = keyArr.length; i < size; ++i) { if ("sign".equals(keyArr[i])) { continue; } sb.append(keyArr[i] + "=" + map.get(keyArr[i]) + "&"); } sb.append("key=" + key); String sign = string2MD5(sb.toString()); return sign; } /*** * MD5加密 生成32位md5碼 */ public static String string2MD5(String str){ if (str == null || str.length() == 0) { return null; } char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; try { MessageDigest mdTemp = MessageDigest.getInstance("MD5"); mdTemp.update(str.getBytes("UTF-8")); byte[] md = mdTemp.digest(); int j = md.length; char buf[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; buf[k++] = hexDigits[byte0 & 0xf]; } return new String(buf).toUpperCase(); } catch (Exception e) { return null; } } }
MapToXmlUtil作用:
由於微信支付接口傳入的參數是xml格式的,因此此類是將map轉化為xml格式
import java.util.Map; /** * Created by Administrator on 16-12-1. */ public class MapToXmlUtil { /** * 將map 轉化成 xml * @param map * @return */ public static String mapToXml(Map<String,String> map)throws Exception{ StringBuilder sb = new StringBuilder(); sb.append("<xml>"); for (Map.Entry<String, String> entry : map.entrySet()) { sb.append("<" + entry.getKey() + ">" + entry.getValue().toString() + "</" + entry.getKey() + ">"); } sb.append("</xml>"); return sb.toString(); } }
最終返回的結果也是xml格式的,為了方便取值,本人轉成json格式;
import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Created by Administrator on 16-12-1. */ public class XMLAndJsonUtil { //用於判斷是否有子節點,若有就將子節點也進行拼接,若無則返回"" public static String checkChildEle(Element element) throws DocumentException { String json=""; List<Element> list = new ArrayList<Element>(); list=element.elements(); if (list.size()>0) { for (Element ele : list) { json+= "'" + ele.getName() + "'" + ":" + "'" + ele.getText() + "'" + "," + "\r\n" + checkChildEle(ele); } } return json; } //這個方法是將xml字符串轉成Json public static String xmlChangeJson(String XML) throws DocumentException{ Document document= DocumentHelper.parseText(XML); Element root=document.getRootElement(); Iterator it=root.elementIterator(); String json="{"; while (it.hasNext()) { Element element =(Element)it.next(); String j=checkChildEle(element); if (j=="") { json+= "'" + element.getName() + "'" + ":" + "'" + element.getText() + "'" + ","+"\r\n"; }else { json+=j; } } json+="}"; return json; } //這個方法是將xml文件轉成Json public static String xmlChangeJson(File XML) throws DocumentException{ SAXReader reader=new SAXReader(); Document document=reader.read(XML); Element root=document.getRootElement(); Iterator it=root.elementIterator(); String json="{"; while (it.hasNext()) { Element element =(Element)it.next(); String j=checkChildEle(element); if (j=="") { json+=element.getName()+":"+element.getText()+","+"\r\n"; }else { json+=j; } } json+="}"; return json; } }
如果報簽名錯誤可以到下面這個網址去驗證sign生成的結果是否一致
https://pay.weixin.qq.com/wiki/tools/signverify/