快速實現微信公眾號支付功能

作者:陳惠,叩丁狼教育高級講師。原創文章,轉載請注明出處。

微信支付類型

微信支付實際上有很多種不同的類型,具體要使用哪一種就需要根據不同的應用場景來選擇,官方給出的參考例子:
刷卡支付:用戶打開微信錢包的刷卡的界面,商戶掃碼后提交完成支付。
公眾號支付:用戶在微信內進入商家H5頁面,頁面內調用JSSDK完成支付
掃碼支付:用戶打開"微信掃一掃“,掃描商戶的二維碼后完成支付
APP支付:商戶APP中集成微信SDK,用戶點擊后跳轉到微信內完成支付
H5支付:用戶在微信以外的手機瀏覽器請求微信支付的場景喚起微信支付
小程序支付:用戶在微信小程序中使用微信支付的場景

本篇文章實現的是公眾號支付,會使用到網頁授權及微信JS-SDK相關知識,但不再詳細介紹
建議大家先閱讀以下文章了解相關內容:
網頁授權:http://www.lxweimin.com/p/94b0e53cccc3
微信JS-SDK:http://www.lxweimin.com/p/b3c4450f845e

實現效果如下動圖:

公眾號支付.gif

公眾號支付相關配置

本篇文章中實現的是公眾號支付,實現條件如下:
1.需要一個已經進行微信認證的公眾號

image.png

2.該公眾號需要開通微信支付功能

image.png

3.到微信商戶平臺https://pay.weixin.qq.com 注冊一個商戶賬號,并關聯你的公眾號,如果需要實現小程序支付的,需要關聯小程序。

image.png

4.擁有一個正式的應用服務器,并且注冊域名
微信支付涉及的私密數據比較多,不允許使用natapp,花生殼之類的內網穿透工具實現,需要有正式的服務器環境,并且要注冊域名,不能使用IP。
比如:http://www.wolfcode.cn

5.相關配置
5.1 配置支付授權目錄,登錄商戶平臺——>產品中心——>開發配置
圖中配置的例子,代表在項目根路徑下,以及web目錄下的頁面都有支付權限,如果不在該路徑的頁面,則無法調用支付功能。
若頁面地址為:http://mywexx.xxxx.com/web/pay.html
則需要配置為:http://mywexx.xxxx.com/web/

image.png

5.2 設置API密鑰,登錄商戶平臺——>賬戶中心——>API安全——>API密鑰

該密鑰在后面的代碼中計算支付簽名的時候需要使用到。

image.png

5.3 配置JS接口安全域名與網頁授權域名,登錄公眾平臺——>公眾號設置——>功能設置
配置網頁授權域名:主要用于獲取用戶的openId,需要識別這是哪個人。
若對openID不了解的同學可先參考微信公眾號開發文檔:https://mp.weixin.qq.com/wiki

配置JS接口安全域名:要讓我們的頁面中彈出輸入密碼的窗口,需要使用微信提供的JS-SDK工具,如果不配置JS接口安全域名,你的頁面無法使用JS-SDK。

image.png

公眾號支付實現流程

大致流程參考官方提供的時序圖:


image.png

流程有很多,不一一演示,我們選取核心部分來實現。

1.提供商城主頁,用戶進入后通過網頁授權獲取openid

image.png

如果對網頁授權不熟悉的同學先參考這篇文章:http://www.lxweimin.com/p/94b0e53cccc3

訪問主頁的地址:http://www.wolfcode.cn/index.do
當用戶第一次打開主頁,默認沒有code參數,此時會先重定向到獲取授權的地址
(如果只需要獲取openid,可以使用scope為snsapi_base靜默授權的方式)
經過授權地址再重定向到我們的index.do時,會帶上code參數,此時即可通過接口獲取用戶的openid

@Controller
public class IndexController {

    @RequestMapping("index")
    public void index(String code, Model model, HttpServletResponse response,HttpServletRequest request)  {
        //如果有code就可以去獲取用戶的openid
        if(code!=null) {
            //通過code來換取access_token
            JSONObject json = WeChatUtil.getWebAccessToken(code);
            //獲取用戶openid
            String openid = json.getString("openid");
            //設置到會話中
            request.getSession().setAttribute("openid",openid);
            //重定向到主頁
            response.sendRedirect("/index.html");
        }else{
            //重定向到授權頁面
            response.sendRedirect(WeChatUtil.WEB_REDIRECT_URL.replace("APPID",WeChatUtil.APPID)
                    .replace("REDIRECT_URI", RequestUtil.getUrl(request)));
        }
    }
}
注意:
 1. WeChatUtil.getWebAccessToken 方法在網頁授權的文章中有介紹。
 2. WEB_REDIRECT_URL 是網頁授權的地址常量:
public static final String WEB_REDIRECT_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
                          "appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base#wechat_redirect";

2.點擊商品后跳轉到商品詳細頁面

具體頁面根據自己的項目添加,主要是頁面需要提供一個可以馬上下訂單的按鈕即可。(這里不演示加入購物車功能)

image.png

點擊立即購買按鈕跳轉到后臺下單地址,并帶上當前商品的id。

<script>
    $(function () {
        //立即購買按鈕
        $("#orderBtn").click(function(){
            //獲取商品id
            var id = $("#productId").val();
            //提交到下單
            window.location.href = "/order.do?productId="+id;
        })
    })
</script>

3.接收商品參數并調用微信支付統一下單接口

正常的業務流程是在該方法中,獲取商品id,再通過id去查詢數據庫該商品的相關屬性,比如名稱,價格等等,然后再創建業務訂單,再去調用微信支付的統一下單接口(讓微信生成預支付單,后續才可以進行支付)。

但此處重點在支付流程,商品的屬性值和訂單相關值,暫且先使用假數據。

接口以及參數可參考微信官方提供的統一下單文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

根據文檔介紹,我們調用統一下單接口時需要帶上相關必填的參數如下:

1.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;
}

調用接口成功后返回的結果也封裝成實體類:

/**
 * 微信統一下單返回結果實體類
 */
@Setter
@Getter
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WxOrderResultEntity {
    private String return_code;
    private String return_msg;
    private String appid;
    private String nonce_str;
    private String sign;
    private String result_code;
    private String trade_type;
    private String prepay_id;
}

該結果中最重要的是prepay_id參數,在頁面中彈出支付窗口時需要用到。

image.png

注意:下單的業務邏輯,正常是需要抽取到業務層的,但是此處為了方便閱讀代碼,直接寫到了控制器上。

@Controller
public class OrderController {

    @RequestMapping("order")
    public String save(Long productId,Model model,HttpServletRequest request) throws Exception {
        //根據商品id查詢商品詳細信息(假數據)
        //productService.getProductById(productId)
        double price = 0.01;//(0.01元)
        String productName = "SweetCity";
        //生成訂單編號
        int number = (int)((Math.random()*9)*1000);//隨機數
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//時間
        String orderNumber = dateFormat.format(new Date()) + number;
        //獲取openId
        String openId = (String) request.getSession().getAttribute("openid");
        //準備調用接口需要的參數
        WxOrderEntity order = new WxOrderEntity();
        //公眾號appid
        order.setAppid(WeChatUtil.APPID);
        //商戶號
        order.setMch_id(WeChatUtil.MCH_ID);
        //商品描述
        order.setBody(productName);
        //設備號,公眾號支付直接填WEB
        order.setDevice_info("WEB");
        //交易類型
        order.setTrade_type("JSAPI");
        //商戶訂單號
        order.setOut_trade_no(orderNumber);
        //支付金額(單位:分)
        order.setTotal_fee((int)(price*100));
        //用戶ip地址
        order.setSpbill_create_ip(RequestUtil.getIPAddress(request));
        //用戶openid
        order.setOpenid(openId);
        //接收支付結果的地址
        order.setNotify_url("http://www.wolfcode.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")){
            //jssdk權限驗證參數
            TreeMap<Object, Object> map = new TreeMap<>();
            map.put("appId",WeChatUtil.APPID);
            long timestamp = new Date().getTime();
            map.put("timestamp",timestamp);//全小寫
            map.put("nonceStr",uuid);
            map.put("signature",WeChatUtil.getSignature(timestamp,uuid,RequestUtil.getUrl(request)));
            model.addAttribute("configMap",map);

            //微信支付權限驗證參數
            String prepayId = entity.getPrepay_id();
            TreeMap<Object, Object> payMap = new TreeMap<>();
            payMap.put("appId",WeChatUtil.APPID);
            payMap.put("timeStamp",timestamp);//駝峰
            payMap.put("nonceStr",uuid);
            payMap.put("package","prepay_id="+prepayId);
            payMap.put("signType","MD5");
            payMap.put("paySign",WeChatUtil.getPaySign(payMap));
            payMap.put("packageStr","prepay_id="+prepayId);
            model.addAttribute("payMap",payMap);
        }
        //跳轉到查看訂單頁面
        return  "order";
    }
}

下面是jssdk中config權限使用到的的簽名,以及微信支付使用的簽名的算法代碼。

官方文檔參考:
config簽名:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
pay簽名:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

    /**
     * 計算jssdk-config的簽名
     * @param timestamp
     * @param noncestr
     * @param url
     * @return
     */
    public static String getSignature(Long timestamp,String noncestr,String url ){
        //對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)
        Map<String,Object> map = new TreeMap<>();
        map.put("jsapi_ticket",getTicket());
        map.put("timestamp",timestamp);
        map.put("noncestr",noncestr);
        map.put("url",url);
        //使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1
        StringBuilder sb = new StringBuilder();
        Set<String> set = map.keySet();
        for (String key : set) {
            sb.append(key+"="+map.get(key)).append("&");
        }
        //去掉最后一個&符號
        String temp = sb.substring(0,sb.length()-1);
        //使用sha1加密
        return SecurityUtil.SHA1(temp);
    }

    /**
     * 計算微信支付的簽名
     * @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();
    }

4.提供訂單展示頁面

image.png

若對微信JS-SDK不了解的同學可先參考該文章:http://www.lxweimin.com/p/b3c4450f845e

在頁面中調用微信JS-SDK,通過config接口注入權限驗證配置,并且添加支付功能。

    <!--jquery-->
    <script src="/js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
    <!--微信的JSSDK-->
    <script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
    <script>
        $(function() {
            <!--通過config接口注入權限驗證配置-->
            wx.config({
                debug: true, // 開啟調試模式
                appId: '${configMap.appId}', // 公眾號的唯一標識
                timestamp: '${configMap.timestamp}', // 生成簽名的時間戳
                nonceStr: '${configMap.nonceStr}', // 生成簽名的隨機串
                signature: '${configMap.signature}',// 簽名
                jsApiList: ['chooseWXPay'] // 填入需要使用的JS接口列表,這里是先聲明我們要用到支付的JS接口
            });

            <!-- config驗證成功后會調用ready中的代碼 -->
            wx.ready(function(){
                //點擊馬上付款按鈕
                $("#payBtn").click(function(){
                    //彈出支付窗口
                    wx.chooseWXPay({
                        timestamp: '${payMap.timeStamp}', // 支付簽名時間戳,
                        nonceStr: '${payMap.nonceStr}', // 支付簽名隨機串,不長于 32 位
                        package: '${payMap.packageStr}', // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=xxxx)
                        signType: '${payMap.signType}', // 簽名方式,默認為'SHA1',使用新版支付需傳入'MD5'
                        paySign: '${payMap.paySign}', // 支付簽名
                        success: function (res) {
                            // 支付成功后的回調函數
                            alert("支付成功!");
                        }
                    });
                })
            });
        });
    </script>

點擊馬上付款后可彈出支付窗口:


zhifu.png

支付完成:


image.png

5.支付結果的處理
當用戶支付后,微信會把支付結果發送到我們前面指定的notify_url地址,我們可以根據支付結果的參數來做相關的業務邏輯,但這里暫不實現,具體支付通知結果的參數可參考官方文章:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容