1. 程式人生 > >電商專案day18(微信支付)

電商專案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.線上簽署協議

本協議為線上電子協議,簽署後方可進行交易及資金結算,簽署完立即生效。

開發文件簡介:

網址:

https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5

我們介紹掃碼支付:native

兩種模式介紹:

第一種模式:微信平臺返回支付的二維碼 

詳細的業務流程,檢視維信開發文件

第二種模式:微信平臺返回一個路徑,我們在客戶端,自己生成,二維碼

同一下單api:

介面連結:

URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder

因為需要的引數很多,我們必須要封裝的引數都要封裝

注:引數值用XML轉義即可,CDATA標籤用於說明資料不被XML解析器解析。

查詢訂單:

應用場景:

該介面提供所有微信支付訂單的查詢,商戶可以通過查詢訂單介面主動查詢訂單狀態,完成下一步的業務邏輯。

需要呼叫查詢介面的情況:

  1. ◆ 當商戶後臺、網路、伺服器等出現異常,商戶系統最終未接收到支付通知;
  2. ◆ 呼叫支付介面後,返回系統錯誤或未知交易狀態情況;
  3. ◆ 呼叫付款碼支付API,返回USERPAYING的狀態;
  4. ◆ 呼叫關單或撤銷介面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中的關聯支付日誌資訊刪除

從新打包,測試