品優購項目記錄:day18

今日目標:

(1)掌握二維碼生成插件 qrious 的使用

(2)理解微信支付開發的整體思路

(3)調用微信支付接口(統一下單)生成支付二維碼

(4)調用微信接口(查詢訂單)查詢支付狀態

(5)實現支付日誌的生成與訂單狀態的修改

 

目錄

1、工程搭建

1.1 建立支付服務接口工程(pay-interface)

1.2 建立支付服務實現工程(pay-service)

2、微信支付二維碼生成

2.1 需求分析

2.2 後端

2.3 前端

3、檢測支付狀態

3.1 需求分析

3.2 後端

3.3 前端

3.4 二維碼超時處理

3.5 支付成功顯示支付金額

4、支付日誌

4.1 需求分析

4.2 插入支付日誌

4.3 讀取支付日誌

5、支付成功,修改訂單狀態


 

1、工程搭建

 

1.1 建立支付服務接口工程(pay-interface)

參考其他服務層接口

 

1.2 建立支付服務實現工程(pay-service)

(1)依賴pay-interface和common,其他依賴參考其他服務層工程

(2)添加微信支付 SDK 依賴

	<dependency>
		<groupId>com.github.wxpay</groupId>
		<artifactId>wxpay-sdk</artifactId>
		<version>0.0.3</version>
	</dependency>

(3)在common工程中放入HttpClient的工具類,並在pom.xml文件中引入依賴

<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>	  		
</dependency>

(4)添加微信支付配置文件

(5)引入 qrious 的js文件到cart-web的plugins目錄中

 

 

 

2、微信支付二維碼生成

 

2.1 需求分析

在支付頁面上生成支付二維碼,並顯示訂單號和金額

用戶拿出手機,打開微信掃描頁面上的二維碼,然後在微信中完成支付

 

2.2 後端

(1)服務層接口(pay-interface),新建WinxinPayService接口

package com.pinyougou.pay.service;

import java.util.Map;

/**
 * 微信支付接口
 * Author xushuai
 * Description
 */
public interface WeixinPayService {

    /**
     * 生成本地支付的數據信息
     *
     * @param out_trade_no 外部訂單號
     * @param total_fee    金額,單位爲:分
     * @return java.util.Map
     */
    Map createNative(String out_trade_no, String total_fee);
}

(2)服務層實現(pay-service),新建WinxinPayServiceImpl實現WinxinPayService

package com.pinyougou.pay.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.github.wxpay.sdk.WXPayUtil;
import com.pinyougou.pay.service.WeixinPayService;
import org.springframework.beans.factory.annotation.Value;
import util.HttpClient;

import java.util.HashMap;
import java.util.Map;

/**
 * 微信支付接口實現
 * Author xushuai
 * Description
 */
@Service
public class WeixinPayServieImpl implements WeixinPayService {

    @Value("${appid}")
    private String WEIXIN_APPID;
    @Value("${partner}")
    private String WEIXIN_PARTNER;
    @Value("${partnerkey}")
    private String WEIXIN_PARTNERKEY;
    @Value("${notifyurl}")
    private String WEIXIN_NOTIFYURL;

    @Override
    public Map createNative(String out_trade_no, String total_fee) {
        try {
            // 1.封裝微信支付請求參數
            Map<String, String> param = buildParam(out_trade_no, total_fee);

            // 2.使用請求參數生成請求xml數據
            String signXml = WXPayUtil.generateSignedXml(param, WEIXIN_PARTNERKEY);
            System.out.println("xml數據:" + signXml);

            // 3.發送請求
            HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
            httpClient.setHttps(true);
            httpClient.setXmlParam(signXml);
            httpClient.post();

            // 4.獲取請求響應結果
            String response = httpClient.getContent();
            Map<String, String> responseMap = WXPayUtil.xmlToMap(response);

            System.out.println("響應結果:" + responseMap);
            // 5.生成返回結果
            if (responseMap.get("return_code").equals("SUCCESS")) {
                Map resultMap = new HashMap();
                resultMap.put("code_url", responseMap.get("code_url"));// 二維碼鏈接地址
                resultMap.put("out_trade_no", out_trade_no);// 訂單號
                resultMap.put("total_fee", total_fee);// 金額

                return resultMap;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 構造微信支付請求參數
     *
     * @param out_trade_no 外部訂單號
     * @param total_fee    金額
     */
    private Map<String, String> buildParam(String out_trade_no, String total_fee) {
        Map<String, String> param = new HashMap<>();
        // 公衆賬號ID
        param.put("appid", WEIXIN_APPID);
        // 商戶號
        param.put("mch_id", WEIXIN_PARTNER);
        // 設備號(自定義參數,可以爲終端設備號(門店號或收銀設備ID),PC網頁或公衆號內支付可以傳"WEB")
        //param.put("device_info", "WEB");
        // 隨機字符串(隨機字符串,長度要求在32位以內。推薦隨機數生成算法)
        param.put("nonce_str", WXPayUtil.generateNonceStr());
        // 商品描述
        param.put("body", "品優購");
        // 商品詳情
        //param.put("detail", WEIXIN_APPID);
        // 商戶訂單號
        param.put("out_trade_no", out_trade_no);
        // 標價金額
        param.put("total_fee", total_fee);
        // 終端IP
        param.put("spbill_create_ip", "127.0.0.1");
        // 通知地址
        param.put("notify_url", "http://test.itcast.cn");
        // 交易類型
        param.put("trade_type", "NATIVE");

        return param;
    }
}

(3)控制層(cart-web),新建PayController

package com.pinyougou.cart.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.pinyougou.pay.service.WeixinPayService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import util.IdWorker;

import java.util.Map;

/**
 * 支付控制層
 * Author xushuai
 * Description
 */
@RestController
@RequestMapping("/pay")
public class PayController {

    @Reference
    private WeixinPayService weixinPayService;

    /**
     * 生成二維碼數據
     * 
     * @return java.util.Map
     */
    @RequestMapping("/createNative")
    public Map createNative(){
        IdWorker idWorker = new IdWorker();
        return weixinPayService.createNative(String.valueOf(idWorker.nextId()), "1");
    }
}

 

 

2.3 前端

(1)新建payService.js

app.service('payService',function($http){
	//本地支付
	this.createNative=function(){
		return $http.get('pay/createNative.do');
	}	
});

(2)新建payController.js 

app.controller('payController' ,function($scope ,payService){	
	//本地生成二維碼
	$scope.createNative=function(){
		payService.createNative().success(
			function(response){
				$scope.money=  (response.total_fee/100).toFixed(2) ;	//金額
				$scope.out_trade_no= response.out_trade_no;//訂單號
				//二維碼
		    	var qr = new QRious({
		 		   element:document.getElementById('qrious'),
		 		   size:250,
		 		   level:'H',
		 		   value:response.code_url
		 		});				
			}
		);		
	}		
});

(3)在pay.html頁面中引入JS文件和基礎指令

(4)設置展示二維碼的img標籤的id爲:qrious

(5)綁定變量,展示訂單ID和訂單總金額

 

 

 

3、檢測支付狀態

 

3.1 需求分析

當用戶支付成功後跳轉到成功頁面

當返回異常時跳轉到錯誤頁面

 

 

3.2 後端

(1)服務層接口(pay-interface),在WinxinPayService新增方法

    /**
     * 查詢訂單支付狀態
     *
     * @param out_trade_no 外部訂單號
     * @return java.util.Map
     */
    Map queryPayStatus(String out_trade_no);

(2)服務層實現(pay-service),在WinxinPayServiceImpl新增實現

@Override
    public Map queryPayStatus(String out_trade_no) {
        try {
            // 1.封裝請求參數
            Map<String, String> param = buildQueryPayStatusParam(out_trade_no);

            // 2.使用請求參數生成請求xml數據
            String signXml = WXPayUtil.generateSignedXml(param, WEIXIN_PARTNERKEY);
            System.out.println("xml數據:" + signXml);

            // 3.發送請求
            HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
            httpClient.setHttps(true);
            httpClient.setXmlParam(signXml);
            httpClient.post();

            // 4.獲取請求結果
            String response = httpClient.getContent();
            Map<String, String> responseMap = WXPayUtil.xmlToMap(response);
            System.out.println("響應結果:" + response);

            return responseMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 構造查詢支付狀態的請求參數
     *
     * @param out_trade_no 商戶訂單號
     * @return java.util.Map<java.lang.String,java.lang.String>
     */
    private Map<String, String> buildQueryPayStatusParam(String out_trade_no) {
        Map<String, String> param = new HashMap<>();
        // 公衆賬號ID
        param.put("appid", WEIXIN_APPID);
        // 商戶號
        param.put("mch_id", WEIXIN_PARTNER);
        // 商戶訂單號
        param.put("out_trade_no", out_trade_no);
        // 隨機字符串
        param.put("nonce_str", WXPayUtil.generateNonceStr());

        return param;
    }

(3)控制層(cart-web),在PayController中新增方法

    /**
     * 查詢訂單狀態(三秒查詢一次,直到支付成功)
     *
     * @param out_trade_no 商家訂單號
     * @return entity.Result
     */
    @RequestMapping("/queryPayStatus")
    public Result queryPayStatus(String out_trade_no){
        Result result = null;
        while (true) {
            // 查詢訂單狀態
            Map map = weixinPayService.queryPayStatus(out_trade_no);
            if (map == null) {
                result = Result.error("支付出錯");
                break;
            }
            if (map.get("trade_state").equals("SUCCESS")) {
                result = Result.success("支付成功");
                break;
            }
            try {
                // 三秒查詢一次訂單狀態
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return result;
    }

 

 

3.3 前端

(1)在payService.js中新增方法

    //查詢支付狀態
    this.queryPayStatus = function (out_trade_no) {
        return $http.get('pay/queryPayStatus.do?out_trade_no=' + out_trade_no);
    }

(2)在payController.js中新增方法

    //查詢支付狀態
    queryPayStatus = function (out_trade_no) {
        payService.queryPayStatus(out_trade_no).success(
            function (response) {
                if (response.success) {
                    location.href = "paysuccess.html";
                } else {
                    location.href = "payfail.html";
                }
            }
        );
    }

(3)在生成二維碼成功響應後,執行查詢訂單狀態的方法

 

 

3.4 二維碼超時處理

(1)後端:在控制層中的PayController中的查詢訂單狀態方法加入邏輯

(2)前端:當二維碼已過期時,重新生成二維碼

 

 

3.5 支付成功顯示支付金額

(1)前端:修改payController中支付成功後的邏輯

(2)前端:在payController中引入$location 服務

 

(3)前端:在payController中新增方法

    //獲取金額
    $scope.getMoney=function(){
        return $location.search()['money'];
    }

(4)前端:在paysuccess.html中引入相關js文件和基礎指令

(5)前端:頁面綁定變量展示支付金額

 

 

 

4、支付日誌

 

4.1 需求分析

我們現在系統還有兩個問題需要解決:

  1. 系統中無法查詢到支付記錄
  2. 支付後訂單狀態沒有改變

我們現在就來解決這兩個問題。

實現思路:

(1)在用戶下訂單時,判斷如果爲微信支付,就想支付日誌表添加一條記錄,信息包括支付總金額、訂單ID(多個)、用戶ID 、下單時間等信息,支付狀態爲0(未支付)

(2)生成的支付日誌對象放入redis中,以用戶ID作爲key,這樣在生成支付二維碼時就可以從redis中提取支付日誌對象中的金額和訂單號。

(3)當用戶支付成功後,修改支付日誌的支付狀態爲1(已支付),並記錄微信傳遞給我們的交易流水號。根據訂單ID(多個)修改訂單的狀態爲2(已付款)。

 

 

4.2 插入支付日誌

(1)生成訂單時,生成支付日誌,修改OrderServiceImpl中的add方法(order-service)

    /**
     * 增加
     */
    @Override
    public void add(TbOrder order) {
        // 1.從redis中提取購物車列表
        List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(order.getUserId());

        List<String> orderIdList = new ArrayList();//訂單ID列表
        double total_money = 0;//總金額 (元)

        // 2.循環購物車保存訂單
        for (Cart cart : cartList) {
            // 生成訂單對象
            TbOrder tbOrder = buildOrder(order);
            // 設置商家ID
            tbOrder.setSellerId(cart.getSellerId());
            // 合計金額
            double money = 0;

            // 循環購物車明細
            for (TbOrderItem orderItem : cart.getOrderItemList()) {
                // 補全訂單明細數據
                orderItem.setOrderId(tbOrder.getOrderId());
                orderItem.setSellerId(cart.getSellerId());
                orderItem.setId(idWorker.nextId());
                // 保存
                orderItemMapper.insert(orderItem);

                // 累加合計金額
                money += orderItem.getTotalFee().doubleValue();
            }
            // 設置合計金額到訂單
            tbOrder.setPayment(BigDecimal.valueOf(money));
            // 保存
            orderMapper.insert(tbOrder);

            // 添加到訂單列表
            orderIdList.add(tbOrder.getOrderId() + "");
            // 累加到總金額
            total_money += money;
        }

        if ("1".equals(order.getPaymentType())) {//如果是微信支付
            TbPayLog payLog = new TbPayLog();
            String outTradeNo = idWorker.nextId() + "";//支付訂單號
            payLog.setOutTradeNo(outTradeNo);//支付訂單號
            payLog.setCreateTime(new Date());//創建時間
            //訂單號列表,逗號分隔
            String ids = orderIdList.toString().replace("[", "").replace("]", "").replace(" ", "");
            payLog.setOrderList(ids);//訂單號列表,逗號分隔
            payLog.setPayType("1");//支付類型
            payLog.setTotalFee((long) (total_money * 100));//總金額(分)
            payLog.setTradeState("0");//支付狀態
            payLog.setUserId(order.getUserId());//用戶ID
            payLogMapper.insert(payLog);//插入到支付日誌表
            redisTemplate.boundHashOps("payLog").put(order.getUserId(), payLog);//放入緩存
        }

        // 3.清除購物車中的數據
        redisTemplate.boundHashOps("cartList").delete(order.getUserId());
    }

 

 

4.3 讀取支付日誌

(1)後端:服務層接口(order-interface),在OrderService中新增方法

	/**
	 * 獲取指定用戶的支付日誌
	 *
	 * @param username 用戶登錄名
	 * @return com.pinyougou.pojo.TbPayLog
	 */
	TbPayLog searchPayLogFromRedis(String username);

(2)後端:服務層實現(order-service),在OrderServiceImpl中實現該方法

    @Override
    public TbPayLog searchPayLogFromRedis(String username) {
        return (TbPayLog) redisTemplate.boundHashOps("payLog").get(username);
    }

(3)後端:控制層(cart-web),修改PayController中的createNative方法的邏輯

    /**
     * 生成二維碼數據
     * 
     * @return java.util.Map
     */
    @RequestMapping("/createNative")
    public Map createNative(){
        // 獲取當前登錄用戶名
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        // 從緩存中取出支付日誌
        TbPayLog tbPayLog = orderService.searchPayLogFromRedis(username);
        if (tbPayLog != null) {
            return weixinPayService.createNative(tbPayLog.getOutTradeNo(), String.valueOf(tbPayLog.getTotalFee()));
        }
        return new HashMap();
    }

 

 

 

5、支付成功,修改訂單狀態

(1)後端:服務層接口(order-interface),在OrderService中新增方法

    /**
     * 修改訂單狀態
     *
     * @param out_trade_no   支付訂單號
     * @param transaction_id 微信返回的交易流水號
     */
    void updateOrderStatus(String out_trade_no, String transaction_id);

(2)後端:服務層實現(order-service),在OrderServiceImpl中實現該方法

    @Override
    public void updateOrderStatus(String out_trade_no, String transaction_id) {
        //1.修改支付日誌狀態
        TbPayLog payLog = payLogMapper.selectByPrimaryKey(out_trade_no);
        payLog.setPayTime(new Date());
        payLog.setTradeState("1");//已支付
        payLog.setTransactionId(transaction_id);//交易號
        payLogMapper.updateByPrimaryKey(payLog);
        //2.修改訂單狀態
        String orderList = payLog.getOrderList();//獲取訂單號列表
        String[] orderIds = orderList.split(",");//獲取訂單號數組

        for (String orderId : orderIds) {
            TbOrder order = orderMapper.selectByPrimaryKey(Long.parseLong(orderId));
            if (order != null) {
                order.setStatus(TbOrder.STATUS_PAY);//已付款
                orderMapper.updateByPrimaryKey(order);
            }
        }
        //清除redis緩存數據
        redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());

    }

(3)後端:控制層(cart-web),在支付成功調用該方法

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章