電商專案day18(微信支付)
今日目標:
二維碼的簡介
二維碼的入門demo
微信平臺支付介面呼叫
檢測支付狀態
支付日誌
一.二維碼簡介以及入門demo
1.簡介:
二維碼又稱 QR Code,QR 全稱 Quick Response,是一個近幾年來移動裝置上超流行的一種編碼方式,它比傳統的 Bar Code 條形碼能存更多的資訊,也能表示更多的資料類
2.優勢
資訊容量大, 可以容納多達 1850 個大寫字母或 2710 個數字或 500 多個漢字
應用範圍廣, 支援文字,聲音,圖片,指紋等等...
容錯能力強, 即使圖片出現部分破損也能使用
成本低, 容易製作
3.二維碼的容錯級別
L 級(低) 7%的碼字可以被恢復。
M 級(中) 的碼字的 15%可以被恢復。
Q 級(四分)的碼字的 25%可以被恢復。
H 級(高) 的碼字的 30%可以被恢復。
4.二維碼的生成外掛qrious
qrious 是一款基於 HTML5 Canvas 的純 JS 二維碼生成外掛。通過 qrious.js 可以快速生成各種二維碼,你可以控制二維碼的尺寸顏色,還可以將生成的二維碼進行 Base64 編碼。
5.入門demo
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <script type="text/javascript" src="js/qrious.min.js"></script> <body> <img id="qrious"> </body> <script type="text/javascript"> var qr = window.qr = new QRious({ element: document.getElementById('qrious'), size: 250, value: 'http://www.baidu.com' }) </script> </html>
匯入qrious的js外掛
二.微信支付簡介
微信掃碼支付的申請步驟:
1.註冊公眾號(型別:服務號)
2.認證公眾號
認證後才能申請 一次300
3.提交申請微信支付材料
登入公眾平臺,點選左側選單【微信支付】,開始填寫資料等待稽核,稽核時間為 1-5個工作日內。
4.開戶成功,登陸商戶平臺進行驗證
資料稽核通過後,請登入聯絡人郵箱查收商戶號和密碼,並登入商戶平臺填寫財付通備付金打的小額資金數額,完成賬戶驗證。
5.線上簽署協議
本協議為線上電子協議,簽署後方可進行交易及資金結算,簽署完立即生效。
開發文件簡介:
網址:
我們介紹掃碼支付:native
兩種模式介紹:
第一種模式:微信平臺返回支付的二維碼
詳細的業務流程,檢視維信開發文件
第二種模式:微信平臺返回一個路徑,我們在客戶端,自己生成,二維碼
同一下單api:
介面連結:
URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
因為需要的引數很多,我們必須要封裝的引數都要封裝
注:引數值用XML轉義即可,CDATA標籤用於說明資料不被XML解析器解析。
查詢訂單:
應用場景:
該介面提供所有微信支付訂單的查詢,商戶可以通過查詢訂單介面主動查詢訂單狀態,完成下一步的業務邏輯。
需要呼叫查詢介面的情況:
- ◆ 當商戶後臺、網路、伺服器等出現異常,商戶系統最終未接收到支付通知;
- ◆ 呼叫支付介面後,返回系統錯誤或未知交易狀態情況;
- ◆ 呼叫付款碼支付API,返回USERPAYING的狀態;
- ◆ 呼叫關單或撤銷介面API之前,需確認支付狀態;
https://api.mch.weixin.qq.com/pay/orderquery
SDK安裝:
維信需要xml格式的資料,我們可以通過維信提供的sdk 來轉
所以我們可以組裝成map格式的資料,然後通過通過SDK轉化為xml格式的字串
我們通過原生的HttpClient來發送請求
在這我們通過工具類來組裝,底層我們封裝
三.電商二維碼生成
思路分析:我們首先封裝,微信介面需要的資料,注意微信要的是xml格式的資料,我們必須通過微信的sdk將map格式轉化為xml格式,然後通過httpClient傳送請求,獲得資料同樣轉化為map格式
後臺程式碼:
@Service
@Transactional
public class PayServiceImpl implements PayService {
//將需要的引數通過value值注入
@Value("${appid}")
private String appid;
@Value("${partner}")
private String partner;
@Value("${partnerkey}")
private String partnerkey;
@Value("${notifyurl}")
private String notifyurl;//回撥地址
@Override
public Map<String, Object> createNative(String out_trade_no, String total_fee) throws Exception {
//1.組裝請求引數
Map<String,String> paramMap = new HashMap<>();
paramMap.put("appid",appid);
paramMap.put("mch_id",partner);
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());//獲得隨機字串
paramMap.put("tbody","品優購");
paramMap.put("tout_trade_no",out_trade_no);//訂單編號
paramMap.put("total_fee",total_fee);//總費用
paramMap.put("spbill_create_ip","127.0.0.1");//本地ip
paramMap.put("tnotify_url",notifyurl);//通知地址
paramMap.put("trade_type","Native");//支付型別
paramMap.put("product_id","1");//這個顯示不是必須傳的,但是native支付至必須傳
//將map格式的資料轉換為xml資料格式
String xmlParam = WXPayUtil.generateSignedXml(paramMap, partnerkey);
//2.基於httpClient傳送請求
HttpClient httpClient = new HttpClient("URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder");
httpClient.setHttps(true);
httpClient.setXmlParam(xmlParam);//設定請求引數
httpClient.post();
//3.處理相應結果
String content = httpClient.getContent();
System.out.println(content);
Map<String, String> resultMap = WXPayUtil.xmlToMap(content);//轉為map資料
//我們必須自己封裝返回給前臺
Map<String,Object> map = new HashMap<>();
map.put("code_url",resultMap.get("code_url"));
map.put("out_trade_no",out_trade_no);
map.put("total_fee",total_fee);
return map;
}
controller層:
public class PayController {
/**
* 生成二維碼
*/
@Reference
private PayService payService;
@RequestMapping("/createNative")
public Map<String,Object> createNative(){
IdWorker idWorker = new IdWorker();
try {
//注意我們在這先是寫死的id號
return payService.createNative(idWorker.nextId()+"","1");
} catch (Exception e) {
e.printStackTrace();
return new HashMap<>();
}
}
}
前臺程式碼:
//控制層
app.controller('payController' ,function($scope,$controller ,payService){
$controller('baseController',{$scope:$scope});//繼承
//生成二維碼
$scope.createNative=function () {
payService.createNative().success(function (response) {
//接受後端傳過來的支付訂單號和支付金額
$scope.out_trade_no=response.out_trade_no;
$scope.total_fee=response.total_fee;
//基於qrious生成二維碼
new QRious({
element: document.getElementById('qrious'),
size: 300,
value: response.code_url,
level:'H'
})
})
}
});
service層:
//服務層
app.service('payService',function($http){
//讀取列表資料繫結到表單中
this.createNative=function(){
return $http.get('pay/createNative.do');
}
});
四.檢測支付狀態
注意:我們在實現過程中,在二維碼生成的時候就繼續呼叫 查詢狀態
/**
* 呼叫查詢狀態介面
* @param out_trade_no
* @return
*/
@Override
public Map queryPayStatus(String out_trade_no) throws Exception {
//1.組裝請求資料
//1、組裝請求引數
Map<String,String> paramMap = new HashMap<>();
paramMap.put("appid",appid);
paramMap.put("mch_id",partner);
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
paramMap.put("out_trade_no",out_trade_no);
String xmlParam = WXPayUtil.generateSignedXml(paramMap, partnerkey);
//2.傳送HttpClient請求
HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
httpClient.setHttps(true);
httpClient.setXmlParam(xmlParam);//設定請求引數
httpClient.post();
//3.返回相應資料
String content = httpClient.getContent();
System.out.println(content);
Map<String, String> xmlMap = WXPayUtil.xmlToMap(content);
return xmlMap;
}
/**
* 查詢支付狀態
*/
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
try {
int count=0;
while (true){
//每隔3秒呼叫一次
Thread.sleep(3000);
//超過5分鐘,跳轉迴圈(支付超時)
count++;
if(count>100){
return new Result(false,"timeout");
}
Map resultMap = payService.queryPayStatus(out_trade_no);
//判斷交易狀態
if(resultMap.get("trade_state").equals("SUCCESS")){
//支付成功後,更新訂單和支付日誌狀態
//payService.updateStatus(out_trade_no, (String) resultMap.get("transaction_id"));
//支付成功
return new Result(true,"支付成功");
}
}
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"支付失敗");
}
}
}
前臺程式碼:
//查詢支付狀態
$scope.queryPayStatus=function () {
payService.queryPayStatus($scope.out_trade_no).success(function (response) {
if(response.success){
//支付成功
location.href="paysuccess.html#?money="+$scope.total_fee;
}else {
if(response.message=="timeout"){
//支付超時
$scope.createNative();
}
//支付失敗
location.href="payfail.html";
}
})
}
//獲取支付金額
$scope.getMoney=function () {
$scope.money=$location.search()["money"];
}
//查詢支付狀態
this.queryPayStatus=function (out_trade_no) {
return $http.get('pay/queryPayStatus.do?out_trade_no='+out_trade_no);
}
注意:路由傳參前面新增#號
五.支付日誌
需求分析以及思路介紹
1、儲存訂單時,插入一條支付操作。(前提:線上支付)tb_pay_log 此時:訂單和支付日誌中的中的支付狀態都是"未支付"
2、當用戶微信掃碼支付成功後,修改訂單和支付日誌中的中的支付狀態,為"已支付",修改支付時間為當前時間。
tb_order tb_order_item
tb_pay_log 支付日誌表,記錄支付行為
以下欄位,是儲存訂單時,記錄一筆支付資訊,需要操作的欄位
`out_trade_no` varchar(30) NOT NULL COMMENT '支付訂單號', //分散式儲存 idWorker
`create_time` datetime DEFAULT NULL COMMENT '建立日期',
`total_fee` bigint(20) DEFAULT NULL COMMENT '支付金額(分)',
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易狀態', //未支付狀態
`user_id` varchar(50) DEFAULT NULL COMMENT '使用者ID',
`order_list` varchar(200) DEFAULT NULL COMMENT '訂單編號列表', //一筆支付可能對應多筆訂單 1,2,3
`pay_type` varchar(1) DEFAULT NULL COMMENT '支付型別', //微信支付
以下欄位是微信支付成功後,需要更新的欄位:
`pay_time` datetime DEFAULT NULL COMMENT '支付完成時間',
`transaction_id` varchar(30) DEFAULT NULL COMMENT '交易號碼', //微信返回,支付成功後需要更新的資料
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易狀態',
儲存訂單的同時,生成一筆支付。
儲存多個訂單,也只生成一筆支付。
線上支付時,儲存支付日誌
讀取支付日誌,顯示支付訂單號和支付金額
支付成功後,修改訂單和支付日誌狀態
`pay_time` datetime DEFAULT NULL COMMENT '支付完成時間',
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易狀態', 已支付:2
`transaction_id` varchar(30) DEFAULT NULL COMMENT '交易號碼',
首先我們在儲存訂單是,插入一條支付操作,使用者儲存支付的所有資訊
//如果是線上支付則,儲存一筆訂單
if (order.getPaymentType().equals("1")){
//建立payLog物件
TbPayLog payLog = new TbPayLog();
/* `out_trade_no` varchar(30) NOT NULL COMMENT '支付訂單號', //分散式儲存 idWorker
`create_time` datetime DEFAULT NULL COMMENT '建立日期',
`total_fee` bigint(20) DEFAULT NULL COMMENT '支付金額(分)',
`trade_state` varchar(1) DEFAULT NULL COMMENT '交易狀態', //未支付狀態
`user_id` varchar(50) DEFAULT NULL COMMENT '使用者ID',
`order_list` varchar(200) DEFAULT NULL COMMENT '訂單編號列表', //一筆支付可能對應多筆訂單 1,2,3
`pay_type` varchar(1) DEFAULT NULL COMMENT '支付型別', //微信支付*/
payLog.setOutTradeNo(idWorker.nextId()+"");
payLog.setCreateTime(new Date());
payLog.setTotalFee((long)(totalMoney*100));//轉化為分
payLog.setTradeState("1");
payLog.setUserId(order.getUserId());
//[1 , 2 , 3]我們通過切割的方式
payLog.setOrderList(ids.toString().replace("[","").replace("]","").replace(" ",""));
payLog.setPayType("1");
//儲存
payLogMapper.insert(payLog);
//將日誌儲存redis中
redisTemplate.boundHashOps("payLog").put(order.getUserId(),payLog);
交易成功後跟新支付日誌
@Autowired
private TbPayLogMapper payLogMapper;
@Autowired
private TbOrderMapper orderMapper;
/**
* 跟新支付日誌狀態
* @param out_trade_no
* @param transaction_id
*/
@Override
public void updateStatus(String out_trade_no, String transaction_id) {
//跟新日誌狀態
TbPayLog payLog = payLogMapper.selectByPrimaryKey(out_trade_no);
payLog.setPayTime(new Date());
payLog.setTradeState("2");
payLog.setTransactionId(transaction_id);
payLogMapper.updateByPrimaryKey(payLog);
//跟新訂單狀態
String orderList = payLog.getOrderList();
String[] split = orderList.split(",");
for (String orderId : split) {
TbOrder tbOrder = orderMapper.selectByPrimaryKey(Long.parseLong(orderId));
tbOrder.setStatus("2");//已支付
tbOrder.setPaymentTime(new Date());
}
//清除當前redis中關聯支付日誌的資訊
redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
}
注意:最後一定把存在redis中的關聯支付日誌資訊刪除
從新打包,測試