微信支付之Native掃碼支付功能
作者:陳惠,叩丁狼教育高階講師。原創文章,轉載請註明出處。
上一篇微信支付文章:https://www.jianshu.com/p/9c322b1a5274
實現了微信公眾號內H5頁面進行支付的功能,但是這種方式的缺點就是必須在微信中開啟付款頁面才能實現,所以並不適合所有的場景。那麼本篇文章,會以另外一種方式實現,使用掃碼的方式來進行支付。
需要注意的是,掃碼支付分兩種形式:
線下的掃碼支付:
這種方式非常簡單,商戶在微信申請付款二維碼貼紙,貼到收銀臺附近位置,客戶購買商品直接使用手機掃碼二維碼,輸入付款金額即可支付,這種方式不需要程式設計人員,大大減少商戶成本,比如常見的便利店,商場線下店等等。
叩丁狼教育.png
線上的掃碼支付:
這是本文選擇實現的方式,也叫做Native支付,這種方式適合PC端的網站,比如大眾點評、攜程、優酷等,如果使用的是電腦來訪問網頁,並需要支付相關費用,比如商品付款,充值VIP這些都是比較常見的場景,很多網站都會選用這種方式,總之最後付款的那一刻,就在網頁上展示付款碼,讓使用者去掃並付款即可,因為這種方式不是面對面付款,所以必須要保證客戶付款的金額是準確的,所以這個二維碼不是固定的,是根據賬單金額生成的,使用者掃碼之後就可以馬上看到需要付款的金額,確認無誤再進行付款。
叩丁狼教育.png
本文使用的是線上的掃碼支付方式,框架使用SpringMVC來實現。
準備工作
一.註冊商戶號
到微信支付商戶平臺https://pay.weixin.qq.com
提交商家相關資料,註冊一個商戶賬號,並開通Native支付功能。
叩丁狼教育.png
二.繫結公眾號或小程式
目的是要得到一個授權的APPID。
叩丁狼教育.png
三.設定API金鑰,登入商戶平臺——>賬戶中心——>API安全——>API金鑰
該金鑰在後面的程式碼中計算支付簽名的時候需要使用到。
叩丁狼教育.png
Native支付實現模式
官方提供了兩種模式實現,第一種模式比較複雜,還需要自己處理二維碼地址相關資訊,第二種比較簡單,微信可以直接把生成好的二維碼地址返回給我們,所以我們使用第二種實現。
具體區別可參考:
實現的大致流程可參考官方提供的時序圖:
叩丁狼教育.png
但是流程有很多,不一一演示,我們選取核心的部分來實現即可。
開發流程
文中的例子為:開通叩丁狼VIP會員服務,並實現掃碼支付。
一.準備一個可以觸發下單操作的頁面
叩丁狼教育.png
二.點選"開通VIP"按鈕後進入controller的方法,接收商品引數並呼叫微信支付統一下單介面
正常的業務流程是在該方法中,獲取商品id,再通過id去查詢資料庫該商品的相關屬性,比如名稱,價格等等,然後再建立應用自身的業務訂單,再去呼叫微信支付的統一下單介面(讓微信生成預支付單,後續才可以進行支付),但此處重點在支付流程,商品的屬性值和訂單相關值,暫且先使用假資料。
介面以及引數可參考微信官方提供的統一下單文件:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
根據文件介紹,使用Native方式呼叫統一下單介面時需要帶上相關必填的引數如下:
叩丁狼教育.png
程式碼如下:
把必填的引數封裝成對應的實體類:
/**
* 微信統一下單實體類
*/
@Setter
@Getter
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WxOrderEntity {
private String appid;
private String body;
private String device_info;
private String mch_id;
private String nonce_str;
private String sign;
private String out_trade_no;
private int total_fee;
private String trade_type;
private String spbill_create_ip;
private String openid;
private String notify_url;
private Long product_id;
}
呼叫介面成功後返回的結果也封裝成實體類:
/**
* 微信統一下單返回結果實體類
*/
@Setter
@Getter
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WxOrderResultEntity {
private String return_code;
private String return_msg;
private String result_code;
private String appid;
private String nonce_str;
private String sign;
private String trade_type;
private String prepay_id;
private String code_url;
}
該結果中最重要的是code_url引數,在生成付款二維碼時需要用到。
叩丁狼教育.png
注意:下單的業務邏輯,正常是需要抽取到業務層的,但是此處為了方便閱讀程式碼,直接寫到了控制器上。
@Controller
public class OrderController {
@RequestMapping("order")
public String save(Long productId, Model model, HttpServletRequest request) throws Exception {
//根據商品id查詢商品詳細資訊(假資料)
double price = 0.01;//(0.01元)
String productName = "叩丁狼VIP會員";
//生成訂單編號
int number = (int)((Math.random()*9)*1000);//隨機數
DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//時間
String orderNumber = dateFormat.format(new Date()) + number;
//準備呼叫介面需要的引數
WxOrderEntity order = new WxOrderEntity();
//appid
order.setAppid(WeChatUtil.APPID);
//商戶號
order.setMch_id(WeChatUtil.MCH_ID);
//商品描述
order.setBody(productName);
//交易型別
order.setTrade_type("NATIVE");
//商戶訂單號
order.setOut_trade_no(orderNumber);
//支付金額(單位:分)
order.setTotal_fee((int)(price*100));
//使用者ip地址
order.setSpbill_create_ip(RequestUtil.getIPAddress(request));
//設定商品id
order.setProduct_id(productId);
//接收支付結果的地址
order.setNotify_url("http:/www.xxxx.com/receive.do");
//32位隨機數(UUID去掉-就是32位的)
String uuid = UUID.randomUUID().toString().replace("-", "");
order.setNonce_str(uuid);
//生成簽名
String sign = WeChatUtil.getPaySign(order);
order.setSign(sign);
//呼叫微信支付統一下單介面,讓微信也生成一個預支付訂單
String xmlResult = HttpUtil.post(GET_PAY_URL, XMLUtil.toXmlString(order));
//把返回的xml字串轉成物件
WxOrderResultEntity entity = XMLUtil.toObject(xmlResult,WxOrderResultEntity.class);
//微信預支付單成功建立
if(entity.getReturn_code().equals("SUCCESS")&&entity.getResult_code().equals("SUCCESS")){
//使用二維碼生成工具,把微信返回的codeUrl轉為二維碼圖片,儲存到磁碟
String codeUrl = entity.getCode_url();
//使用訂單號來作為二維碼的圖片名稱
File file = new File(QRCodeUtil.PAY_PATH,orderNumber+".jpg");
QRCodeUtil.createImage(codeUrl,new FileOutputStream(file));
//把訂單號傳到支付頁面
model.addAttribute("orderNumber",orderNumber);
}
//跳轉到支付頁進行支付
return "pay";
}
}
統一下單介面呼叫成功後,微信會建立一個預支付單,此時即為未付款狀態:
叩丁狼教育.png
同時,微信還會返回codeUrl給我們,這個就是二維碼的連結,我們需要利用工具把該連結轉為二維碼
叩丁狼教育.png
二維碼生成工具,我使用的是google的Zxing
maven依賴:
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
工具類:
public class QRCodeUtil {
// 二維碼尺寸
public static final int QRCODE_SIZE = 300;
// 存放二維碼的路徑
public static final String PAY_PATH = "c://pay";
/**
* 生成二維碼
* @param content 源內容
* @param outputStream 輸出流
* @throws Exception
*/
public static void createImage(String content, OutputStream outputStream) throws Exception {
Hashtable hints = new Hashtable();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
// 存到磁碟
ImageIO.write(image, "jpg", outputStream);
}
}
生成二維碼網上也有很多工具,有的人還會在二維碼中間加入logo,自己去找就可以啦。
下面是剛才統一下單的介面使用到的簽名演算法程式碼。
官方文件參考:
pay簽名:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
/**
* 計算微信支付的簽名
* @param obj
* @return
* @throws IllegalAccessException
*/
public static String getPaySign(Object obj) throws IllegalAccessException, IOException {
StringBuilder sb = new StringBuilder();
//把物件轉為TreeMap集合(按照key的ASCII 碼從小到大排序)
TreeMap<String, Object> map;
if(!(obj instanceof Map)) {
map = ObjectUtils.objectToMap(obj);
}else{
map = (TreeMap)obj;
}
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
//遍歷鍵值對
for (Map.Entry<String, Object> entry : entrySet) {
//如果值為空,不參與簽名
if(entry.getValue()!=null) {
//格式key1=value1&key2=value2…
sb.append(entry.getKey() + "=" + entry.getValue() + "&");
}
}
//最後拼接商戶的API金鑰
String stringSignTemp = sb.toString()+"key="+WeChatUtil.KEY;
//進行md5加密並轉為大寫
return SecurityUtil.MD5(stringSignTemp).toUpperCase();
}
三.支付頁面,展示付款二維碼
效果如下:
叩丁狼教育.png
頁面程式碼:
<div class="mod_layer_wxopen" style="display:block;">
<iframe frameborder="0" class="iframe_mask"></iframe>
<div class="ly_content">
<div class="ly_bd cf">
<div class="ly_ct">
<div class="qr_list">
<h3 class="qr_tit">正在給微信帳號<span class="user js_wx_name">H</span><span class="js_txt">開通</span>VIP會員</h3>
<div class="qr_pic">
<img id="qr_img" src="/getQrCode.do?orderNumber=${orderNumber}">
<span class="icon_wx"></span>
</div>
<p class="qr_txt js_qr_txt">使用微信掃描二維碼</p>
<p class="qr_tips">請使用微信掃碼支付</p>
</div>
</div>
</div>
</div>
</div>
重點的是img標籤,二維碼圖片是經過controller去找的。
獲取付款二維碼:
@RequestMapping("getQrCode")
public void getQrCode(String orderNumber,HttpServletResponse response) throws IOException {
//從磁碟中獲取付款二維碼並輸出給response
File file = new File(QRCodeUtil.PAY_PATH,orderNumber+".jpg");
if(file.exists()){
IOUtils.copy(new FileInputStream(file),response.getOutputStream());
}
}
有了付款二維碼,客戶就可以使用微信掃碼,並支付了。
四.支付結果的處理
當用戶支付後,微信會把支付結果傳送到我們之前指定的notify_url地址,我們可以根據支付結果來做相關的業務邏輯
把微信支付通知結果封裝成實體類
@Setter
@Getter
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WxPayResultEntity {
private String return_code;
private String result_code;
private String return_msg;
private String transaction_id;//微信支付訂單號
private String out_trade_no;//商戶訂單號
private String total_fee;//訂單金額
private String cash_fee;//現金支付金額
}
具體支付通知結果的引數可參考官方文章:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
接收支付結果並處理業務:
@RequestMapping("receive")
@ResponseBody
public BaseResult receive(@RequestBody WxPayResultEntity result) throws IOException {
//判斷是否支付成功
if(result.getReturn_code().equals("SUCCESS")&&result.getResult_code().equals("SUCCESS")){
//避免結果出現差異,安全起見,會再呼叫預支付訂單查詢的介面,檢查該訂單的狀態是否是已支付
//程式碼省略
//.....
}
//通知微信我們收到了,如果微信沒有收到回覆,會間隔一段時間又通知一遍,這樣的話容易出現業務重複處理操作
BaseResult resp = new BaseResult();
resp.setReturn_code("SUCCESS");
resp.setReturn_msg("OK");
return resp;
}
主要邏輯是:
判斷微信返回的支付結果是否支付成功,如果是支付成功,還應呼叫查詢預支付訂單的介面,再次確認支付結果,如果確認無誤,我們就可以執行支付成功的業務邏輯,比如設定業務訂單狀態為已付款,商品發貨,或者設定為VIP會員等等,最後需要給微信返回應答,通知微信我們收到並處理了這個結果,如果微信沒有收到我們的回覆,會間隔一段時間又再次通知一遍,這樣的話容易出現業務重複處理的問題,可能導致商家資金損失。
查詢預支付訂單參考:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2
處理支付結果參考:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
五.頁面輪詢檢查支付結果
為了使用者體驗更好,頁面需要定時去獲取支付的結果,及時跳轉頁面或者顯示支付結果給客戶。
頁面帶上訂單號輪詢檢查:
<script type="text/javascript">
//每兩秒檢查一次
setInterval(function() {
$.get("/checkOrder.do?orderNumber=${orderNumber}",function(data) {
if (data.success) {
alert("支付成功!");
}
});
},2000)
</script>
後臺查詢該訂單的支付狀態:
在該方法中,應查詢資料庫,檢查該業務訂單是否為已支付的狀態,如果是,返回success:true,頁面接收到結果,即可馬上提示支付成功或者跳轉到我的訂單頁面之類的業務處理。
@RequestMapping("checkOrder")
@ResponseBody
public JSONObject checkOrder(String orderNumber) throws IOException {
//檢查訂單狀態,確認已支付,返回success:true
//邏輯省略....
JSONObject json = new JSONObject();
json.put("success",true);
return json;
}
想獲取更多技術乾貨,請前往叩丁狼官網:http://www.wolfcode.cn/all_article.html