2020微信支付之PC網站(Native)支付詳解

瞭解支付模式
Native

適用場景:
Native支付是指商戶系統按微信支付協議生成支付二維碼,用戶再用微信“掃一掃”完成支付的模式。該模式適用於PC網站、實體店單品或訂單、媒體廣告支付等場景。

解釋:
這裏描述一下PC端業務場景,例如:你在網站上購買東西,選擇好商品後,會生成一個支付二維碼,掃碼支付後,商品的庫存就減少了。 還有,自動販賣機,選擇商品後會生成支付二維碼,掃碼支付後,商品就會掉下來。
開發前準備
申請認證公衆號 (若已有公衆號則可跳過, 注意: 1. 公衆號類型需要爲服務號或者訂閱號 2. 公衆號需要認證, 認證費用300RMB)點此跳轉申請公衆號
申請網站備案 (若網站已備案則可跳過,注意:網站的備案內容需要和所出售商品直接關係)
有以上兩個條件後可以開始申請開通微信支付,具體的申請流程公衆號平臺都有,讀者可自行去查閱。
注意:1.開通微信支付需要公司賬戶打款驗證,要及時和財務溝通好,查看公司流水。2.開通成功後,會收到郵件 ,郵件中的信息特別重要:示例如下

以上三條均申請完成即可開始配置開發環境。拿到郵件中的信息後,按照郵件中的指引配置好相關信息之後就可以開始開發了。
注意:很重要的幾個信息,
API證書
API密鑰
APPID
開發工具:本文采用的是idea,其他IDE開發工具均可
環境準備:
添加依賴
        <!-- 添加微信支付jar包 -->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- 二維碼生成依賴 -->
        <!-- 二維碼 -->
        <!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>2.1</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
示例代碼目錄


支付:
    /**
     * 映射微信支付
     *
     * @param amount  金額(單位:分)
     * @return
     */
    @RequestMapping("/wxpay")
    public void wxPay(Integer amount,HttpServletResponse response) throws Exception {
        // 金額
        Double total_fee = amount.doubleValue();
        // 生成商家訂單號
        String orderId = PayUtil.createOrder();
        if (total_fee > 0) {
            WXPayService wxPayService = WXPayService.getInstance(certPath, notifyUrl);
            String codeUrl = wxPayService.doUnifiedOrder(orderId, "收銀臺名稱", total_fee, "產品id");

            //  ---------- 訂單保存到數據庫 ----------
            // 這裏寫你自己的訂單保存邏輯
            // BizOrder bizOrder = new BizOrder();
            // bizOrder.setAmount(amount);
            // bizOrder.setAppid(PayConstant.APPID);
            // bizOrder.setMacId(orderId);
            // bizOrder.setTotalFee(String.valueOf(total_fee));
            // bizOrder.setOrgId(Long.parseLong(orgId));
            // BizOrder bo = bizOrderService.insertOrder(bizOrder);
            // if (bo == null)
            //    throw new NullPointerException("Don't save order!");

            // 生成支付二維碼(數據流) 返回給前端頁面
            QRCodeUtil.createQrCodeStream(codeUrl,900,"JPEG",response);
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
支付業務
   /**
 * 微信支付業務
 * Created by Jason on 2018/11/20.
 */
public class WXPayService {
    private static Logger logger = LoggerFactory.getLogger(WXPayService.class);
    private WXPay wxpay;
    private WXPayConfigImpl config;
    private static WXPayService wxPayService;

    private WXPayService(String certPath, String notifyUrl) throws Exception {
        config = WXPayConfigImpl.getInstance(certPath,notifyUrl);
        wxpay = new WXPay(config);
    }

    public static WXPayService getInstance(String certPath,String notifyUrl) throws Exception {
        if (wxPayService == null) {
            synchronized (WXPayConfigImpl.class) {
                if (wxPayService == null) {
                    wxPayService = new WXPayService(certPath,notifyUrl);
                }
            }
        }
        return wxPayService;
    }

    /**
     * 微信下單接口
     *
     * @param out_trade_no 訂單號
     * @param body         商家商品名
     * @param money        總金額
     * @param applyNo      商品編號
     * @return
     */
    public String doUnifiedOrder(String out_trade_no, String body, Double money, String applyNo) {
        String amt = String.valueOf(money * 100);
        HashMap<String, String> data = new HashMap<String, String>();
        data.put("body", body);
        data.put("out_trade_no", out_trade_no);
        data.put("device_info", "web");
        data.put("fee_type", "CNY");
        data.put("total_fee", amt.substring(0, amt.lastIndexOf(".")));
        data.put("spbill_create_ip", config.getSpbillCreateIp());
        data.put("notify_url", config.getNotifyUrl());
        data.put("trade_type", config.getTradeType());
        data.put("product_id", applyNo);
        System.out.println(String.valueOf(money * 100));
        // data.put("time_expire", "20170112104120");

        try {
            Map<String, String> r = wxpay.unifiedOrder(data);
            logger.info("返回的參數是" + r);
            return r.get("code_url");
        } catch (Exception e) {
            e.printStackTrace();
            logger.info(e.getMessage());
            return null;
        }
    }

    /**
     * 退款
     */
    public void doRefund(String out_trade_no, String total_fee) {
        logger.info("退款時的訂單號爲:" + out_trade_no + "退款時的金額爲:" + total_fee);
        String amt = String.valueOf(Double.parseDouble(total_fee) * 100);
        logger.info("修正後的金額爲:" + amt);
        logger.info("最終的金額爲:" + amt.substring(0, amt.lastIndexOf(".")));
        HashMap<String, String> data = new HashMap<String, String>();
        data.put("out_trade_no", out_trade_no);
        data.put("out_refund_no", out_trade_no);
        data.put("total_fee", amt.substring(0, amt.lastIndexOf(".")));
        data.put("refund_fee", amt.substring(0, amt.lastIndexOf(".")));
        data.put("refund_fee_type", "CNY");
        data.put("op_user_id", config.getMchID());

        try {
            Map<String, String> r = wxpay.refund(data);
            logger.info("退款操作返回的參數爲" + r);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 微信驗籤接口
     *
     * @return
     * @throws DocumentException
     */
    public boolean checkSign(String  strXML) throws DocumentException {
        SortedMap<String, String> smap = new TreeMap<String, String>();
        Document doc = DocumentHelper.parseText(strXML);
        Element root = doc.getRootElement();
        for (Iterator iterator = root.elementIterator(); iterator.hasNext();) {
            Element e = (Element) iterator.next();
            smap.put(e.getName(), e.getText());
        }
        return isWechatSign(smap,config.getKey());
    }


    private boolean isWechatSign(SortedMap<String, String> smap,String apiKey) {
        StringBuffer sb = new StringBuffer();
        Set<Entry<String, String>> es = smap.entrySet();
        Iterator<Entry<String, String>> it = es.iterator();
        while (it.hasNext()) {
            Entry<String, String> entry =  it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!"sign".equals(k) && null != v && !"".equals(v) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + apiKey);
        /** 驗證的簽名 */
        String sign = MD5Util.MD5Encode(sb.toString(), "utf-8").toUpperCase();
        /** 微信端返回的合法簽名 */
        String validSign = ((String) smap.get("sign")).toUpperCase();
        return validSign.equals(sign);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
支付回調
/**
     * 微信支付成功回調接口
     *
     * @param map      返回給頁面
     * @param request  接收請求
     * @param response 響應
     * @return
     * @throws Exception
     */
    @RequestMapping("/callback")
    public void callback(Map<String, Object> map, HttpServletRequest request,
                           HttpServletResponse response) throws Exception {

        log.info("用戶支付成功,回調!");
        String inputLine;
        String notityXml = "";
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        // 微信給返回的東西
        try {
            while ((inputLine = request.getReader().readLine()) != null) {
                notityXml += inputLine;
            }
            request.getReader().close();
        } catch (Exception e) {
            e.printStackTrace();
            log.info("xml獲取失敗");
            response.getWriter().write(setXml("FAIL", "xml獲取失敗"));
        }
        if (StringUtils.isEmpty(notityXml)) {
            log.info("xml爲空");
            response.getWriter().write(setXml("FAIL", "xml爲空"));
        }

        WXPayService wxService = WXPayService.getInstance(certPath, notifyUrl);
        if (!wxService.checkSign(notityXml)) {
            response.getWriter().write(setXml("FAIL", "驗籤失敗"));
        }
        log.info("xml的值爲:" + notityXml);
        Map<String, String> xmlMap = WXPayUtil.xmlToMap(notityXml); // 解析成map
        String json = JSON.toJSONString(xmlMap); // map 轉成json
        log.info(json);
        JSONObject jsonObject = JSONObject.fromObject(json);
        UnifiedOrderRespose returnPay = (UnifiedOrderRespose) JSONObject.toBean(jsonObject, UnifiedOrderRespose.class);
        log.info(("轉換後對象爲:" + returnPay.toString()));
        log.info(("訂單號:" + returnPay.getOut_trade_no() + "總金額:" + returnPay.getTotal_fee()));
        if (returnPay.getReturn_code().equals("SUCCESS") && returnPay.getOut_trade_no() != null
                && !returnPay.getOut_trade_no().isEmpty()) {
            double fee = Double.parseDouble(returnPay.getTotal_fee());
            returnPay.setTotal_fee(String.valueOf(fee / 100));
            log.info("微信的支付狀態爲SUCCESS");
            // 支付成功業務
            synchronized (WXPayController.class) { // 加入同步鎖
                Thread.sleep(1000); //睡一秒,防止併發倒致數據不一致
//                addServiceCount(returnPay,response); // 寫你支付成功後的業務, 比如給用戶充值服務次數
            }
            // 返回false的原因有可能是:訂單已完成支付,或者訂單已退款
        }else
            response.getWriter().write(setXml("FAIL","ORDER IS FINISH"));

        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
二維碼生成
/**
     * 創建二維碼輸入流
     * @param content        二維碼內容
     * @param qrCodeSize    二維碼尺寸
     * @param imageFormat    圖片格式
     * @param response        響應給頁面
     */
    public static void createQrCodeStream(String content, int qrCodeSize, String imageFormat, HttpServletResponse response) throws IOException, WriterException {
        //設置二維碼糾錯級別MAP
        Hashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<EncodeHintType, ErrorCorrectionLevel>();
        hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);  // 矯錯級別
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        //創建比特矩陣(位矩陣)的QR碼編碼的字符串
        BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap);
        // 使BufferedImage勾畫QRCode  (matrixWidth 是行二維碼像素點)
        int matrixWidth = byteMatrix.getWidth();
        BufferedImage image = new BufferedImage(matrixWidth-200, matrixWidth-200, BufferedImage.TYPE_INT_RGB);
        image.createGraphics();
        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, matrixWidth, matrixWidth);
        // 使用比特矩陣畫並保存圖像
        graphics.setColor(Color.BLACK);
        for (int i = 0; i < matrixWidth; i++){
            for (int j = 0; j < matrixWidth; j++){
                if (byteMatrix.get(i, j)){
                    graphics.fillRect(i-100, j-100, 1, 1);
                }
            }
        }
        // 返回給頁面
        OutputStream outputStream= response.getOutputStream();
        // 關流
        try {
            ImageIO.write(image, imageFormat, outputStream);
        } finally {
            outputStream.close();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
講解一下,前端發送請求過來,具體攜帶的參數,因人而異。攜帶的是前端生成的訂單號,以及支付價格。微信的支付價格一般是分, Controller 接收到請求後,拿到參數,然後下單對接微信平臺,下單成功會返回一個二維碼的url,然後自行生成二維碼。將二維碼返回給頁面,用戶掃碼支付後,微信平臺會回調你所填寫的異步回調通知接口,驗證簽名之後,解析平臺給你返回的參數。
 

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