此項目已開源歡迎Start、PR、發起Issues一起討論交流共同進步
https://github.com/Javen205/IJPay
http://git.oschina.net/javen205/IJPay
微信極速開發系列文章:http://www.jianshu.com/p/a172a1b69fdd
上一篇文章介紹了微信提供的那些支付方式以及公眾號支付http://www.jianshu.com/p/cb2456a2d7a7
這篇文章我們來聊聊微信掃碼支付(模式一以及模式二)
微信掃碼支付文檔
掃碼支付分為以下兩種方式:
【模式一】:商戶后台系統根據微信支付規則鏈接生成二維碼,鏈接中帶固定參數productid(可定義為產品標識或訂單號)。用戶掃碼后,微信支付系統將productid和用戶唯一標識(openid)回調商戶后台系統(需要設置支付回調URL),商戶后台系統根據productid生成支付交易,最后微信支付系統發起用戶支付流程。
【模式二】:商戶后台系統調用微信支付統一下單API生成預付交易,將接口返回的鏈接生成二維碼,用戶掃碼后輸入密碼完成支付交易。注意:該模式的預付單有效期為2小時,過期后無法支付。詳細接入步
掃碼支付模式一
1、設置支付回調URL
商戶支付回調URL設置指引:進入公眾平台-->微信支付-->開發配置-->掃碼支付-->修改 如下圖(來自官方文檔)
在開源項目weixin-guide中
掃碼支付模式一
的回調URL為http://域名[/項目名稱]/pay/wxpay
2、根據微信支付規則鏈接生成二維碼
2.1 生成二維碼規則
二維碼中的內容為鏈接,形式為:
weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
詳細的參數說明參考文檔 點擊這里
商戶ID(mch_id)如何獲取點擊這里
簽名安全規則文檔 點擊這里
在開源項目weixin-guide中
掃碼支付模式一
生成二維碼規則封裝如下:
public String getCodeUrl(){
String url="weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXX&time_stamp=XXXXX&nonce_str=XXXXX";
String product_id="001";
String timeStamp=Long.toString(System.currentTimeMillis() / 1000);
String nonceStr=Long.toString(System.currentTimeMillis());
Map<String, String> packageParams = new HashMap<String, String>();
packageParams.put("appid", appid);
packageParams.put("mch_id", partner);
packageParams.put("product_id",product_id);
packageParams.put("time_stamp", timeStamp);
packageParams.put("nonce_str", nonceStr);
String packageSign = PaymentKit.createSign(packageParams, paternerKey);
return StringUtils.replace(url, "XXXXX", packageSign,appid,partner,product_id,timeStamp,nonceStr);
}
以上action
在開源項目weixin-guide中 訪問地址為http://域名[/項目名稱]/pay/getCodeUrl
其中 product_id
根據實際的業務邏輯可以當做參數傳入
2.2 生成二維碼並在頁面上顯示
根據2.1生成二維碼規則生成了二維碼中的內容(鏈接)來生成二維碼。
商戶可調用第三方庫生成二維碼圖片
這里使用google 開源圖形碼工具Zxing
項目中引入相關的jar包 具體配置參考項目中的pom.xml
<!-- 版本號-->
<zxing.version>3.2.1</zxing.version>
<!-- 開源多維碼生成工具 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${zxing.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${zxing.version}</version>
</dependency>
封裝的工具類為com.javen.kit.ZxingKit
/**
* google 開源圖形碼工具Zxing使用
*/
public class ZxingKit {
private static Log log = Log.getLog(ZxingKit.class.getSimpleName());
/**
* Zxing圖形碼生成工具
*
* @param contents
* 內容
* @param barcodeFormat
* BarcodeFormat對象
* @param format
* 圖片格式,可選[png,jpg,bmp]
* @param width
* 寬
* @param height
* 高
* @param margin
* 邊框間距px
* @param saveImgFilePath
* 存儲圖片的完整位置,包含文件名
* @return
*/
public static Boolean encode(String contents, BarcodeFormat barcodeFormat, Integer margin,
ErrorCorrectionLevel errorLevel, String format, int width, int height, String saveImgFilePath) {
Boolean bool = false;
BufferedImage bufImg;
Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
// 指定糾錯等級
hints.put(EncodeHintType.ERROR_CORRECTION, errorLevel);
hints.put(EncodeHintType.MARGIN, margin);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
try {
// contents = new String(contents.getBytes("UTF-8"), "ISO-8859-1");
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, barcodeFormat, width, height, hints);
MatrixToImageConfig config = new MatrixToImageConfig(0xFF000001, 0xFFFFFFFF);
bufImg = MatrixToImageWriter.toBufferedImage(bitMatrix, config);
bool = writeToFile(bufImg, format, saveImgFilePath);
} catch (Exception e) {
e.printStackTrace();
}
return bool;
}
/**
* @param srcImgFilePath
* 要解碼的圖片地址
* @return
*/
@SuppressWarnings("finally")
public static Result decode(String srcImgFilePath) {
Result result = null;
BufferedImage image;
try {
File srcFile = new File(srcImgFilePath);
image = ImageIO.read(srcFile);
if (null != image) {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
result = new MultiFormatReader().decode(bitmap, hints);
} else {
log.debug("Could not decode image.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
return result;
}
}
/**
* 將BufferedImage對象寫入文件
*
* @param bufImg
* BufferedImage對象
* @param format
* 圖片格式,可選[png,jpg,bmp]
* @param saveImgFilePath
* 存儲圖片的完整位置,包含文件名
* @return
*/
@SuppressWarnings("finally")
public static Boolean writeToFile(BufferedImage bufImg, String format, String saveImgFilePath) {
Boolean bool = false;
try {
bool = ImageIO.write(bufImg, format, new File(saveImgFilePath));
} catch (Exception e) {
e.printStackTrace();
} finally {
return bool;
}
}
public static void main(String[] args) {
String saveImgFilePath = "D://zxing.png";
Boolean encode = encode("我是Javen205", BarcodeFormat.QR_CODE, 3, ErrorCorrectionLevel.H, "png", 200, 200,
saveImgFilePath);
if (encode) {
Result result = decode(saveImgFilePath);
String text = result.getText();
System.out.println(text);
}
}
}
OK 上面就是生成支付二維碼的部分,接下來就是要將二維碼顯示在頁面上,於是就有了下面的代碼:
com.javen.weixin.controller.WeixinPayController.getPayQRCode()
src\\main\\webapp\\view\\payQRCode.jsp
/**
* 生成支付二維碼(模式一)並在頁面上顯示
*/
public void scanCode1(){
//獲取掃碼支付(模式一)url
String qrCodeUrl=getCodeUrl();
System.out.println(qrCodeUrl);
//生成二維碼保存的路徑
String name = "payQRCode.png";
Boolean encode = ZxingKit.encode(qrCodeUrl, BarcodeFormat.QR_CODE, 3, ErrorCorrectionLevel.H, "png", 200, 200,
PathKit.getWebRootPath()+File.separator+"view"+File.separator+name );
if (encode) {
//在頁面上顯示
setAttr("payQRCode", name);
render("payQRCode.jsp");
}
}
JSP 部分代碼如下
<body>
<img alt="" src="<%=path %>/view/${payQRCode}">
</body>
最終生成二維碼訪問地址為
http://域名[/項目名稱]/pay/scanCode1
以上就是微信掃碼支付(模式一)生成支付二維碼的全過程
3、掃碼回調商戶支付URL
用戶掃碼后,微信支付系統將productid和用戶唯一標識(openid)回調商戶后台系統。
此回調的URL為上文
設置支付回調的URL
。特別要注意的是返回參數是xml輸入流
HttpServletRequest request = getRequest();
/**
* 獲取用戶掃描二維碼后,微信返回的信息
*/
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(),"utf-8");
System.out.println("callBack_xml>>>"+result);
<xml>
<appid><![CDATA[wx5e9360a3f46f64cd]]></appid>
<openid><![CDATA[o_pncsidC-pRRfCP4zj98h6slREw]]></openid>
<mch_id><![CDATA[商戶ID]]></mch_id>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<nonce_str><![CDATA[gT5NJAlv9eXawn1j]]></nonce_str>
<product_id><![CDATA[001]]></product_id>
<sign><![CDATA[D2BD7949269271B3112B442421B43D66]]></sign>
</xml>
4、根據回調參數生成預付訂單進行支付
根據回調參數調用統一下單API生成預支付交易的prepay_id
prepay_xml>>>
<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[微信的appid]]></appid>
<mch_id><![CDATA[商戶ID]]></mch_id>
<nonce_str><![CDATA[p46NAoD82eAH2d9j]]></nonce_str>
<sign><![CDATA[4117C601F41533DC84159AF6B892F72D]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201610151315007cb1cbe40b0064755332]]></prepay_id>
<trade_type><![CDATA[NATIVE]]></trade_type>
<code_url><![CDATA[weixin://wxpay/bizpayurl?pr=QCLqJIG]]></code_url>
</xml>
商戶后台系統將prepay_id返回給微信支付系統,微信支付系統根據交易會話標識,發起用戶端授權支付流程。
/**
* 發送信息給微信服務器
*/
Map<String, String> payResult = PaymentKit.xmlToMap(xmlResult);
String return_code = payResult.get("return_code");
String result_code = payResult.get("result_code");
if (StrKit.notBlank(return_code) && StrKit.notBlank(result_code) && return_code.equalsIgnoreCase("SUCCESS")&&result_code.equalsIgnoreCase("SUCCESS")) {
// 以下字段在return_code 和result_code都為SUCCESS的時候有返回
String prepay_id = payResult.get("prepay_id");
Map<String, String> prepayParams = new HashMap<String, String>();
prepayParams.put("return_code", "SUCCESS");
prepayParams.put("appId", appid);
prepayParams.put("mch_id", mch_id);
prepayParams.put("nonceStr", System.currentTimeMillis() + "");
prepayParams.put("prepay_id", prepay_id);
String prepaySign = null;
if (sign.equals(packageSign)) {
prepayParams.put("result_code", "SUCCESS");
}else {
prepayParams.put("result_code", "FAIL");
prepayParams.put("err_code_des", "訂單失效"); //result_code為FAIL時,添加該鍵值對,value值是微信告訴客戶的信息
}
prepaySign = PaymentKit.createSign(prepayParams, paternerKey);
prepayParams.put("sign", prepaySign);
String xml = PaymentKit.toXml(prepayParams);
log.error(xml);
renderText(xml);
}
5、支付結果通用通知
官方文檔 點擊這里
對后台通知交互時,如果微信收到商戶的應答不是成功或超時,微信認為通知失敗,微信會通過一定的策略定期重新發起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。 (通知頻率為15/15/30/180/1800/1800/1800/1800/3600,單位:秒)
注意:同樣的通知可能會多次發送給商戶系統。商戶系統必須能夠正確處理重復的通知。
推薦的做法是,當收到通知進行處理時,首先檢查對應業務數據的狀態,判斷該通知是否已經處理過,如果沒有處理過再進行處理,如果處理過直接返回結果成功。在對業務數據進行狀態檢查和處理之前,要采用數據鎖進行並發控制,以避免函數重入造成的數據混亂。
特別提醒:商戶系統對於支付結果通知的內容一定要做簽名驗證,防止數據泄漏導致出現“假通知”,造成資金損失。
技術人員可登進微信商戶后台掃描加入接口報警群。
此通知接收地址為生成預付訂單時設置的notify_url
。在開源項目weixin-guide中通知默認的地址為http://域名[/項目名稱]/pay/pay_notify
以上是微信掃碼支付模式一的全過程。
掃碼支付模式二
模式二與模式一相比,流程更為簡單,不依賴設置的回調支付URL。商戶后台系統先調用微信支付的統一下單接口,微信后台系統返回鏈接參數code_url,商戶后台系統將code_url值生成二維碼圖片,用戶使用微信客戶端掃碼后發起支付。注意:code_url有效期為2小時,過期后掃碼不能再發起支付。
微信支付的統一下單接口具體實現上文也有提及到,如果還不是很清楚可以看 com.javen.weixin.controller.WeixinPayController
中的scanCode2
以及官方文檔介紹
以下是調用預付訂單返回的xml
<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx5e9360a3f46f64cd]]></appid>
<mch_id><![CDATA[1322117501]]></mch_id>
<nonce_str><![CDATA[XdVf2zXLErIHRfJn]]></nonce_str>
<sign><![CDATA[916703CD13C3615B9B629C4A9E4C3337]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx2016101514433661797ee3010493199442]]></prepay_id>
<trade_type><![CDATA[NATIVE]]></trade_type>
<code_url><![CDATA[weixin://wxpay/bizpayurl?pr=WWOXnrb]]></code_url>
</xml>
其中code_url
就是生成二維碼的鏈接
String qrCodeUrl = result.get("code_url");
String name = "payQRCode1.png";
Boolean encode = ZxingKit.encode(qrCodeUrl, BarcodeFormat.QR_CODE, 3, ErrorCorrectionLevel.H, "png", 200, 200,
PathKit.getWebRootPath()+File.separator+"view"+File.separator+name );
if (encode) {
//在頁面上顯示
setAttr("payQRCode", name);
render("payQRCode.jsp");
}
掃碼即可進行支付,code_url有效期為2小時,過期后掃碼不能再發起支付
最終生成二維碼訪問地址為
http://域名[/項目名稱]/pay/scanCode2
碼字完畢,以上就是微信掃碼支付(模式一、模式二)的詳細介紹。
歡迎留言、轉發
微信極速開發系列文章:http://www.jianshu.com/p/a172a1b69fdd
后續更新預告
1、刷卡支付
2、微信紅包
3、企業轉賬