- 接入掃碼支付(包含PC網站支付)包含三個階段,問這裏只講使用,也就是第2階段的《啓動設計和開發》。
- 點擊查看開發者文檔(掃碼支付)後,這裏感覺微信的文檔沒有支付寶好理解(稍微吐槽下~~~),不過我們忽略一切,直接進入模式二:模式二最簡單直接,不需要在商戶後臺進行配置,推薦大家使用,微信也說流程更爲簡單,我這裏也講的是模式二,模式一大家有興趣可以自行研究下。
- 如上圖,總流程有14步,主要流程是生成訂單、調統一下單API、將返回的支付交易鏈接生成二維碼展示;我這邊主要就是將這三步結合springmvc後,成功生兒二維碼之後,用戶就可以掃碼支付了。後面的回調跟跟我的另一篇博文基本類似,大家借鑑下就行了:支付寶:web頁面掃碼支付、網站支付、支付寶即時到賬 + springmvc
四、實現:
- 準備:根據統一下單接口API我先定義了三個對象:UnifiedOrderRequest(統一下單請求參數(必填))、UnifiedOrderRequestExt(統一下單請求參數(非必填))、UnifiedOrderRespose(統一下單返回參數);具體如下代碼,get、set方法可自行生產,太佔篇幅。
UnifiedOrderRequest.class- /**
- * 統一下單請求參數(必填)
- * @author Y
- *
- */
- public class UnifiedOrderRequest {
- private String appid; //公衆賬號ID
- private String mch_id; //商戶號
- private String nonce_str; //隨機字符串
- private String sign; //簽名
- private String body; //商品描述
- private String out_trade_no; <span style="white-space:pre"> </span>//商戶訂單號
- private String total_fee; //總金額
- private String spbill_create_ip; <span style="white-space:pre"> </span>//終端IP
- private String notify_url; //通知地址
- private String trade_type; //交易類型
- }
- /**
- * 統一下單請求參數(非必填)
- * @author Y
- *
- */
- public class UnifiedOrderRequestExt extends UnifiedOrderRequest{
- private String device_info; //設備號
- private String detail; //商品詳情
- private String attach; //附加數據
- private String fee_type; //貨幣類型
- private String time_start; //交易起始時間
- private String time_expire; //交易結束時間
- private String goods_tag; //商品標記
- private String product_id; //商品ID
- private String limit_pay; //指定支付方式
- private String openid; //用戶標識
- }
- /**
- * 統一下單返回參數
- * @author Y
- *
- */
- public class UnifiedOrderRespose {
- private String return_code; //返回狀態碼
- private String return_msg; //返回信息
- private String appid; //公衆賬號ID
- private String mch_id; //商戶號
- private String device_info; //設備號
- private String nonce_str; //隨機字符串
- private String sign; //簽名
- private String result_code; //業務結果
- private String err_code; //錯誤代碼
- private String err_code_des; <span style="white-space:pre"> </span>//錯誤代碼描述
- private String trade_type; //交易類型
- private String prepay_id; //預支付交易會話標識
- private String code_url; //二維碼鏈接
- }
- Controller主入口:
- /**
- * 創建二維碼
- */
- @RequestMapping("createQRCode")
- public void createQRCode(String orderId, HttpServletResponse response) {
- //生成訂單
- String orderInfo = createOrderInfo(orderId);
- //調統一下單API
- String code_url = httpOrder(orderInfo);
- //將返回預支付交易鏈接(code_url)生成二維碼圖片
- //這裏使用的是zxing <span style="color:#ff0000;"><strong>說明1(見文末)</strong></span>
- try {
- int width = 200;
- int height = 200;
- String format = "png";
- Hashtable hints = new Hashtable();
- hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
- BitMatrix bitMatrix = new MultiFormatWriter().encode(code_url, BarcodeFormat.QR_CODE, width, height, hints);
- OutputStream out = null;
- out = response.getOutputStream();
- MatrixToImageWriter.writeToStream(bitMatrix, format, out);
- out.flush();
- out.close();
- } catch (Exception e) {
- }
- }
- 生成訂單:分兩部分:一部分是業務需求的訂單信息,就是發起支付前的訂單信息,業務系統自行創建存儲;另一部分是滿足統一下單API要求的訂單信息(也是我們這裏要講的)。“xxxxxx”:是你需要自己填寫的對應信息:
- /**
- * 生成訂單
- * @param orderId
- * @return
- */
- private String createOrderInfo(String orderId) {
- //生成訂單對象
- UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest();
- unifiedOrderRequest.setAppid("xxxxxxxxxxxxx");//公衆賬號ID
- unifiedOrderRequest.setMch_id("xxxxxxxxx");//商戶號
- unifiedOrderRequest.setNonce_str(StringUtil.makeUUID());//隨機字符串 <span style="color:#ff0000;"><strong>說明2(見文末)</strong></span>
- unifiedOrderRequest.setBody("xxxxxx");//商品描述
- unifiedOrderRequest.setOut_trade_no(orderId);//商戶訂單號
- unifiedOrderRequest.setTotal_fee("x"); //金額需要擴大100倍:1代表支付時是0.01
- unifiedOrderRequest.setSpbill_create_ip("xxxxxxxxxxxxx");//終端IP
- unifiedOrderRequest.setNotify_url("xxxxxxxxxxxxxx");//通知地址
- unifiedOrderRequest.setTrade_type("NATIVE");//JSAPI--公衆號支付、NATIVE--原生掃碼支付、APP--app支付
- unifiedOrderRequest.setSign(createSign(unifiedOrderRequest));//簽名<span style="color:#ff0000;"><strong>說明5(見文末,簽名方法一併給出)</strong></span>
- //將訂單對象轉爲xml格式
- XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_"))); //<span style="color:#ff0000;"><strong>說明3(見文末)</strong></span>
- xStream.alias("xml", UnifiedOrderRequest.class);//根元素名需要是xml
- return xStream.toXML(unifiedOrderRequest);
- }
- 調統一下單API:根據要求將生成訂單中返回的xml向微信給定的統一下單URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder,發送請求,成功並獲得二維碼。
- /**
- * 調統一下單API
- * @param orderInfo
- * @return
- */
- private String httpOrder(String orderInfo) {
- String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
- try {
- HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
- //加入數據
- conn.setRequestMethod("POST");
- conn.setDoOutput(true);
- BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
- buffOutStr.write(orderInfo.getBytes());
- buffOutStr.flush();
- buffOutStr.close();
- //獲取輸入流
- BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
- String line = null;
- StringBuffer sb = new StringBuffer();
- while((line = reader.readLine())!= null){
- sb.append(line);
- }
- XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));//說明3(見文末)
- //將請求返回的內容通過xStream轉換爲UnifiedOrderRespose對象
- xStream.alias("xml", UnifiedOrderRespose.class);
- UnifiedOrderRespose unifiedOrderRespose = (UnifiedOrderRespose) xStream.fromXML(sb.toString());
- //根據微信文檔return_code 和result_code都爲SUCCESS的時候纔會返回code_url
- //<span style="color:#ff0000;"><strong>說明4(見文末)</strong></span>
- if(null!=unifiedOrderRespose
- && "SUCCESS".equals(unifiedOrderRespose.getReturn_code())
- && "SUCCESS".equals(unifiedOrderRespose.getResult_code())){
- return unifiedOrderRespose.getCode_url();
- }else{
- return null;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- 將返回的支付交易鏈接生成二維碼展示:沒有異常的情況下,在頁面中使用<img>標籤接收就行。實際使用時,結合前端和業務的需求放置二維碼。可以在掃碼支付/案例及規範中找到部分素材和界面規範來設計微信風格的支付頁面。
- <img src="${ctx}/wxPay/createQRCode?orderId=1111" width="174px">
- 用戶可以通過維繫客戶端進行掃碼支付。支付完成後回調我們notify_url設置的url,通過成功的回調來更改業務系統中的訂單狀態或者一些業務需求。這裏回調沒有寫出可以參考支付寶:web頁面掃碼支付、網站支付、支付寶即時到賬 + springmvc中的回調。
五、說明:
- 二維碼可以查看zxing實現二維碼生成和解析;微信這邊也提供了二維碼的學習,大家有興趣可以看看:http://www.thonky.com/qr-code-tutorial/ 和http://coolshell.cn/articles/10590.html
- 隨機字符串:微信對隨機字符串的要求是不超過32位。我這邊是這樣生成的,用時間戳。
- /**
- * 創建UUID
- * @return
- */
- public static synchronized String makeUUID() {
- Date date = new Date();
- StringBuffer s = new StringBuffer(DateUtil.formatYmdhmsm(date));
- return s.append((new Random().nextInt(900) + 100)).toString();
- }
- 使用Xstream時,由於微信定義的變量名大部分使用了“_”,但是在Xstream中它是關鍵字,所以會自動變爲“__”,引起報錯。詳情請看:XStream異常:對象轉爲XML時,會把"_"轉成"__";報錯:(Lcom/thoughtworks/xstream/io/naming/NameCoder;)V
- 獲取二維碼鏈接時,只有在return_code 和result_code都爲SUCCESS的時候有返回;這裏我就簡單的滿足時返回,不滿足返回null,您寫的時候需要結合業務考慮下,是否需要增加判斷,從而滿足不同的業務場景。統一下單API
- 簽名在上面一直沒有詳細說明,首先查看微信的安全規範中籤名算法。key值,需要自己填寫
- /**
- * 生成簽名
- *
- * @param appid_value
- * @param mch_id_value
- * @param productId
- * @param nonce_str_value
- * @param trade_type
- * @param notify_url
- * @param spbill_create_ip
- * @param total_fee
- * @param out_trade_no
- * @return
- */
- private String createSign(UnifiedOrderRequest unifiedOrderRequest) {
- //根據規則創建可排序的map集合
- SortedMap<String, String> packageParams = new TreeMap<String, String>();
- packageParams.put("appid", unifiedOrderRequest.getAppid());
- packageParams.put("body", unifiedOrderRequest.getBody());
- packageParams.put("mch_id", unifiedOrderRequest.getMch_id());
- packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str());
- packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
- packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());
- packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());
- packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());
- packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());
- StringBuffer sb = new StringBuffer();
- Set es = packageParams.entrySet();//字典序
- Iterator it = es.iterator();
- while (it.hasNext()) {
- Map.Entry entry = (Map.Entry) it.next();
- String k = (String) entry.getKey();
- String v = (String) entry.getValue();
- //爲空不參與簽名、參數名區分大小寫
- if (null != v && !"".equals(v) && !"sign".equals(k)
- && !"key".equals(k)) {
- sb.append(k + "=" + v + "&");
- }
- }
- //第二步拼接key,key設置路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設置-->API安全-->密鑰設置
- sb.append("key=" +"xxxxxxxxxxxxxxxxx");
- String sign = MD5Util.MD5Encode(sb.toString(), "utf-8")
- .toUpperCase();//MD5加密
- return sign;
- }