微信支付之公衆號支付

經過近一週的敲代碼,終於把公衆號支付和H5支付實現完成並測試通過,特此分享一些流程,一方面自己記錄另一方面給新入門的一點思路

【本文介紹普通商戶的公衆號支付】

一、基本信息和配置

公衆號支付的前提是要有一個擁有支付功能的公衆號和一個已經通過ICP備案的域名,這裏不再贅述,如果你申請支付成功,將會收到以下樣子的郵件:
這裏寫圖片描述
接下來,你就可以通過登陸賬號和密碼進入到微信支付商戶平臺,配置基本信息。

1.設置API密鑰。
初次登陸沒有密鑰的需要設置一個具體在 賬戶中心>API安全,下圖示:
這裏寫圖片描述

先裝個證書然後可以設置密鑰(生成一個32位的UUID設爲密鑰即可,注意:密鑰不能查看只能設置),以後在接口簽名時需要這個東西,設置完成如下圖示:
這裏寫圖片描述

2.設置開發配置。
在產品中心>開發配置中添加公衆號支付授權目錄,注意是“目錄”,並非域名,
你需要將項目發起支付的接口最後一個斜槓之前的地址配置進去,
如:http://www.bbcd.com/projectName/phone/gotoPayJS.do ,
你就需要添加http://www.bbcd.com/projectName/phone/這個地址
這裏寫圖片描述
3.登陸微信公衆平臺,在 公衆號設置>功能設置下配置網頁授權域名,注意這裏是域名。

通過以上三步配置就結束了,接下來就是敲代碼了。

二、開發支付

強烈建議參考微信公衆號開發文檔
(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1)
總結下來微信公衆號支付開發主要兩步驟:
1. 通過統一下單接口發起請求,並獲得prepay_id(預支付交易會話標識),這個標示是你接下來想微信提交支付的關鍵數據;
2. 在微信瀏覽器內H5調起支付。
是不是感覺很簡單呀,接下來兩步驟解說。

一)發起統一下單接口

接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
需求參數:

字段名 變量名 類型 示例值 描述
公衆賬號ID appid String(32) wxd678efh567hg6787 微信支付分配的公衆賬號ID(企業號corpid即爲此appId)
商戶號 mch_id String(32) 1230000109 微信支付分配的商戶號
隨機字符串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 隨機字符串,長度要求在32位以內。推薦隨機數生成算法
簽名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 通過簽名算法計算得出的簽名值,詳見簽名生成算法
商品描述 body String(128) 騰訊充值中心-QQ會員充值 商品簡單描述,該字段請按照規範傳遞,具體請見參數規定
商戶訂單號 out_trade_no String(32) 20150806125346 商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-
標價金額 total_fee Int 88 訂單總金額,單位爲分,詳見支付金額
終端IP spbill_create_ip String(16) 123.12.12.123 APP和網頁支付提交用戶端ip,Native支付填調用微信支付API的機器IP。
通知地址 notify_url String(256) http://www.weixin.qq.com/wxpay/pay.php 異步接收微信支付結果通知的回調地址,通知url必須爲外網可訪問的url,不能攜帶參數。
交易類型 trade_type String(16) JSAPI 取值如下:JSAPI,NATIVE,APP等,說明詳見參數規定
用戶標識 openid String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI時(即公衆號支付),此參數必傳,此參數爲微信用戶在商戶對應appid下的唯一標識。openid如何獲取,可參考【獲取openid】。企業號請使用【企業號OAuth2.0接口】獲取企業號內成員userid,再調用【企業號userid轉openid接口】進行轉換
設備號 device_info String(32) 013467007045764 自定義參數,可以爲終端設備號(門店號或收銀設備ID),PC網頁或公衆號內支付可以傳”WEB”

我定義了一個bean存儲,API裏有各種字段,在這裏我們只保留自己需要的,主要構造方法裏我加上了幾個固定字段的初始化,公衆號,商戶號,隨機數,設備類型(”WEB”),通知地址。
這裏我着重說下通知地址這個字段,它是公衆號支付完成後微信端向我們發起回調的地址,告訴我們交易的結果,這個地址不能加參數,注意此回調必須向微信端做出迴應,否則微信會認爲我們沒有正確接收結果,會持續回調多次(下文我會細說迴應)

package com.wtp.wechat.bean;

import com.wtp.wechat.util.CommonUtil;
import com.wtp.wechat.util.WechatPayUtil;
import com.wtp.wechat.util.WechatUtil;

/**
 * 
 * @ClassName: UnifiedOrder 
 * @Description: 統一下單
 * @author tianpengw 
 * @date 2017年10月12日 上午9:57:39 
 *
 */
public class UnifiedOrder {
    /**
     * 公衆賬號ID
     */
    private String appid;
    /**
     * 商戶號
     */
    private String mch_id;
    /**
     * 隨機串
     */
    private String nonce_str;
    /**
     * 簽名
     */
    private String sign;
    /**
     * 商品描述
     */
    private String body;
    /**
     * 商戶訂單號
     */
    private String out_trade_no;
    /**
     * 總金額
     */
    private Integer total_fee;
    /**
     * 終端IP(用戶)
     */
    private String spbill_create_ip;
    /**
     * 通知地址
     */
    private String notify_url;
    /**
     * 交易類型
     */
    private String trade_type;
    /**
     * 用戶標識
     */
    private String openid;
    /**
     * WEB
     */
    private String device_info;

    public UnifiedOrder(){
        this.appid = WechatUtil.appid;
        this.mch_id = WechatPayUtil.mchId;
        this.nonce_str = CommonUtil.getUUID();
        this.device_info = WechatPayUtil.deviceInfo;
        this.notify_url = WechatPayUtil.notifyUrl;
    }

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getMch_id() {
        return mch_id;
    }

    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    public String getNonce_str() {
        return nonce_str;
    }

    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }

    public String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }

    public String getOut_trade_no() {
        return out_trade_no;
    }

    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }

    public String getSpbill_create_ip() {
        return spbill_create_ip;
    }

    public void setSpbill_create_ip(String spbill_create_ip) {
        this.spbill_create_ip = spbill_create_ip;
    }

    public Integer getTotal_fee() {
        return total_fee;
    }

    public void setTotal_fee(Integer total_fee) {
        this.total_fee = total_fee;
    }

    public String getTrade_type() {
        return trade_type;
    }

    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public String getDevice_info() {
        return device_info;
    }

    public void setDevice_info(String device_info) {
        this.device_info = device_info;
    }
}

下面是微信支付相關的幾個方法

package com.wtp.wechat.util;

import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.wtp.wechat.bean.UnifiedOrder;
/**
 * 
 * @ClassName: WechatPayUtil 
 * @Description: 微信支付工具類
 * @author tianpengw 
 * @date 2017年10月12日 下午3:38:25 
 *
 */
public class WechatPayUtil {

    private static Logger log = LogManager.getLogger(WechatPayUtil.class);
    /**
     * 微信支付分配的商戶號
     */
    public static final String mchId = PropertiesUtil.getProperties("pay.mchid");
    /**
     * 商戶平臺密鑰
     */
    public static final String apiKey = PropertiesUtil.getProperties("pay.apikey");
    /**
     * 微信支付分配的商戶號
     */
    public static final String notifyUrl = PropertiesUtil.getProperties("pay.notifyUrl");
    /**
     * 終端設備號(門店號或收銀設備ID),注意:PC網頁或公衆號內支付請傳"WEB"
     */
    public static String deviceInfo = "WEB";
    /**
     * JSAPI -- 公衆號支付
     */
    public static String tradeTypeJs = "JSAPI";
    /**
     * MWEB -- H5支付
     */
    public static String tradeTypeH5 = "MWEB";
    /**
     * 統一訂單地址
     */
    private static String unifiedUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 
     * @Description: 公衆號支付-統一訂單類型獲得prepay_id
     * @author tianpengw 
     * @param uo
     * @return
     */
    public static String getUnifiedOrderPId(UnifiedOrder uo){
        String sign = createUnifiedOrderSign(uo);
        uo.setSign(sign);
        String xml = XMLBeanUtils.objectToXMLStr(uo);
        log.info("公賬號支付統一訂單請求參數:"+xml);
        String res = HttpHelper.httpsRequest(unifiedUrl,"POST",xml);
        log.info("公賬號支付統一訂單返回結果:"+res);
        Map<String, String> responseMap = XMLBeanUtils.readStringXmlOut(res);
        return responseMap.get("prepay_id");
    }

    /**
     * 
     * @Description: 微信支付 -統一訂單類型獲得mweb_url
     * @author tianpengw 
     * @param uo
     * @return
     */
    public static String getUnifiedOrderMWebUrl(UnifiedOrder uo){
        String sign = createUnifiedOrderSign(uo);
        uo.setSign(sign);
        String xml = XMLBeanUtils.objectToXMLStr(uo);
        log.info("H5支付統一訂單請求參數:"+xml);
        String res = HttpHelper.httpsRequest(unifiedUrl,"POST",xml);
        log.info("H5支付統一訂單返回結果:"+res);
        Map<String, String> responseMap = XMLBeanUtils.readStringXmlOut(res);
        return responseMap.get("mweb_url");
    }

    /**
     * 獲取統一下單簽名
     * @param unifiedOrder
     * @return
     */
    private static String createUnifiedOrderSign(UnifiedOrder unifiedOrder){
        StringBuffer sign = new StringBuffer();
        sign.append("appid=").append(unifiedOrder.getAppid());
        sign.append("&body=").append(unifiedOrder.getBody());
        sign.append("&device_info=").append(unifiedOrder.getDevice_info());
        sign.append("&mch_id=").append(unifiedOrder.getMch_id());
        sign.append("&nonce_str=").append(unifiedOrder.getNonce_str());
        sign.append("&notify_url=").append(unifiedOrder.getNotify_url());
        /**
         * H5支付簽名時沒有用戶的openId
         */
        if(!CommonUtil.isEmpty(unifiedOrder.getOpenid())){
            sign.append("&openid=").append(unifiedOrder.getOpenid());
        }
        sign.append("&out_trade_no=").append(unifiedOrder.getOut_trade_no());
        sign.append("&spbill_create_ip=").append(unifiedOrder.getSpbill_create_ip());
        sign.append("&total_fee=").append(unifiedOrder.getTotal_fee());
        sign.append("&trade_type=").append(unifiedOrder.getTrade_type());
        sign.append("&key=").append(apiKey);

        return SignatureUtil.MD5(sign.toString()).toUpperCase();
    }
}

getUnifiedOrderPId是獲得prepareId的方法
getUnifiedOrderMWebUrl是獲得微信H5支付的地址,這裏先不用管,下章再說
createUnifiedOrderSign這個方法是獲得sign簽名的方法,apiKey就是上面商戶平臺設置的API密鑰。

用到幾個工具類的方法我也列舉出來:
1)xstream 生成xml格式的字符串和解析字符串類xml爲map的方法

package com.wtp.wechat.util;

import java.io.InputStream;

import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.core.util.QuickWriter;

public class XMLBeanUtils {

    private static XStream xStream = new XStream(new XppDriver(new NoNameCoder()){
        @Override
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 對所有xml節點的轉換都增加CDATA標記
                boolean cdata = true;

                @Override
                @SuppressWarnings("rawtypes")
                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                @Override
                public String encodeNode(String name) {
                    return name;
                }


                @Override
                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("<![CDATA[");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });

    /**
     * 
     * @Description: bean轉xml字符串
     * @author tianpengw 
     * @param obj
     * @return
     */
    public static String objectToXMLStr(Object obj){
        xStream.alias("xml", obj.getClass());
        return xStream.toXML(obj);
    }

     /** 
     * 解析微信發來的請求(XML) 
     *  
     * @param request 
     * @return 
     * @throws Exception 
     */  
    @SuppressWarnings("unchecked")  
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {  
        // 將解析結果存儲在HashMap中  
        Map<String, String> map = new HashMap<String, String>();  

        // 從request中取得輸入流  
        InputStream inputStream = request.getInputStream();  
        // 讀取輸入流  
        SAXReader reader = new SAXReader();  
        Document document = reader.read(inputStream);  
        // 得到xml根元素  
        Element root = document.getRootElement();  
        // 得到根元素的所有子節點  
        List<Element> elementList = root.elements();  
        // 遍歷所有子節點  
        for (Element e : elementList){
            map.put(e.getName(), e.getText());  
        }
        // 釋放資源  
        inputStream.close();  
        inputStream = null;  

        return map;  
    }

    /**
     * @description 將xml字符串轉換成map
     * @param xml
     * @return Map
     */
    public static Map<String, String> readStringXmlOut(String xml) {
        Map<String, String> map = new HashMap<String, String>();
        Document doc = null;
        try {
            doc = DocumentHelper.parseText(xml); // 將字符串轉爲XML
            Element rootElt = doc.getRootElement(); // 獲取根節點
            @SuppressWarnings("unchecked")
            List<Element> list = rootElt.elements();// 獲取根節點下所有節點
            for (Element element : list) { // 遍歷節點
                map.put(element.getName(), element.getText()); // 節點的name爲map的key,text爲map的value
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

}

2)發起Http請求方法

    /**
      * 
      * @Description: 發起Http請求
      * @author tianpengw 
      * @param requestUrl 請求地址
      * @param requestMethod 請求方式 GET/POST
      * @param outputStr 請求參數,如果沒有置 null
      * @return
      */
    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr){    
        try {    
            URL url = new URL(requestUrl);    
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();    

            conn.setDoOutput(true);    
            conn.setDoInput(true);    
            conn.setUseCaches(false);    
            // 設置請求方式(GET/POST)    
            conn.setRequestMethod(requestMethod);    
            conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");    
            // 當outputStr不爲null時向輸出流寫數據    
            if (null != outputStr) {    
                OutputStream outputStream = conn.getOutputStream();    
                // 注意編碼格式    
                outputStream.write(outputStr.getBytes("UTF-8"));    
                outputStream.close();    
            }    
            // 從輸入流讀取返回內容    
            InputStream inputStream = conn.getInputStream();    
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");    
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);    
            String str = null;  
            StringBuffer buffer = new StringBuffer();    
            while ((str = bufferedReader.readLine()) != null) {    
                buffer.append(str);    
            }    
            // 釋放資源    
            bufferedReader.close();    
            inputStreamReader.close();    
            inputStream.close();    
            inputStream = null;    
            conn.disconnect();    
            return buffer.toString();    
        } catch (Exception e) {
            e.printStackTrace();
        }    
        return null;    
    } 

3)MD5加密方法

   /**
     * 
     * @Description: 生成 MD5
     * @author tianpengw 
     * @param data
     * @return
     */
    public static String MD5(String data){
        StringBuilder sb = new StringBuilder();
        try {
            java.security.MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

4)獲得客戶端IP方法

   /**
     * 
     * @Description: 獲取客戶端地址
     * @author tianpengw 
     * @param request
     * @return
     */
    public static String getClientIP(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");  
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {  
            ipAddress = request.getHeader("Proxy-Client-IP");  
        }  
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {  
            ipAddress = request.getHeader("WL-Proxy-Client-IP");  
        }  
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {  
            ipAddress = request.getRemoteAddr();  
            if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){  
                //根據網卡取本機配置的IP  
                InetAddress inet=null;  
                try {  
                    inet = InetAddress.getLocalHost();  
                } catch (UnknownHostException e) {  
                    e.printStackTrace();  
                }  
                ipAddress= inet.getHostAddress();  
            }  
        }  
        //對於通過多個代理的情況,第一個IP爲客戶端真實IP,多個IP按照','分割  
        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15  
            if(ipAddress.indexOf(",")>0){  
                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));  
            }  
        }  
        return ipAddress;
    }

5)獲得UUID

   /**
     *   
     * @Description: 獲得一個UUID
     * @author tianpengw 
     * @return String
     */
    public static String getUUID(){  
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);  
    }

6)timstamp生成

String timestamp = Long.toString(System.currentTimeMillis() / 1000);

7)是否是微信瀏覽器判斷

   /**
     * 
     * @Description: 判斷是否是微信瀏覽器發起的請求,如果是返回true,反正返回false
     * @author tianpengw 
     * @param req
     * @return
     */
    public static boolean isWechatBrowser(HttpServletRequest req){
        String ua = req.getHeader("user-agent").toLowerCase();  
        if (ua.indexOf("micromessenger") >= 0) {// 是微信瀏覽器  
            return true;  
        } 
        return false;
    }

方法我都列出來,至於業務相關的訂單id,商品body(注意內容不要太長限制爲128,本人在這裏吃過虧)等字段都需要自己加入自此獲得了寶貴的prepared_id。

二)微信瀏覽器內調起支付

帶着上文的prepared_id我們開始向頁面出發,再出發之前要把通關文件都一一準備好,讓我們看下一個通關類:

package com.wtp.wechat.bean;
/**
 * 
 * @ClassName: JSAPIConfig 
 * @Description: JSAPI使用的配置信息
 * @author tianpengw 
 * @date 2017年10月12日 下午3:44:48 
 *
 */
public class JSAPIConfig {

    /**
     * 公衆號id:商戶註冊具有支付權限的公衆號成功後即可獲得
     */
    private String appId;
    /**
     * 時間戳
     */
    private String timeStamp;
    /**
     * 隨機字符串,不長於32位
     */
    private String nonceStr;
    /**
     * 簽名
     */
    private String paySign;
    /**
     * 簽名算法,暫支持MD5
     */
    private String signType;
    /**
     * 訂單詳情擴展字符串-package
     * 統一下單接口返回的prepay_id參數值,提交格式如:prepay_id=***
     */
    private String packageName;

    public JSAPIConfig(){
        this.signType = "MD5";
    }
    public String getAppId() {
        return appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getTimeStamp() {
        return timeStamp;
    }
    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }
    public String getNonceStr() {
        return nonceStr;
    }
    public void setNonceStr(String nonceStr) {
        this.nonceStr = nonceStr;
    }
    public String getPaySign() {
        return paySign;
    }
    public void setPaySign(String paySign) {
        this.paySign = paySign;
    }
    public String getSignType() {
        return signType;
    }
    public void setSignType(String signType) {
        this.signType = signType;
    }
    public String getPackageName() {
        return packageName;
    }
    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }
}

此bean因微信的內置對象WeixinJSBridge而出現,接口示例如下:

在微信瀏覽器裏面打開H5網頁中執行JS調起支付。接口輸入輸出數據格式爲JSON。
注意:WeixinJSBridge內置對象在其他瀏覽器中無效。
WeixinJSBridge.invoke(
   'getBrandWCPayRequest', {
       "appId":"wx2421b1c4370ec43b",     //公衆號名稱,由商戶傳入     
       "timeStamp":"1395712654",         //時間戳,自1970年以來的秒數     
       "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //隨機串     
       "package":"prepay_id=u802345jgfjsdfgsdg888",     
       "signType":"MD5",         //微信簽名方式:     
       "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信簽名 
   },
   function(res){     
       if(res.err_msg == "get_brand_wcpay_request:ok" ) {}     
       // 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回ok,但並不保證它絕對可靠。 
   }
   ); 

這幾個字段裏唯一注意的是paySign簽名,我爲了省事,通過拼字符串來進行MD5加密(其他用到的方法我上面列舉的都有):

StringBuffer sign = new StringBuffer();
sign.append("appId=").append(WechatUtil.appid);
sign.append("&nonceStr=").append(nonce);
sign.append("&package=").append(packageName);
sign.append("&signType=").append(config.getSignType());
sign.append("&timeStamp=").append(timestamp);
sign.append("&key=").append(WechatPayUtil.apiKey);

String signature = SignatureUtil.MD5(sign.toString()).toUpperCase();

我部分代碼如下:

後臺部分代碼:
這裏寫圖片描述
前臺部分代碼:

//對瀏覽器的UserAgent進行正則匹配,不含有微信獨有標識的則爲其他瀏覽器
var useragent = navigator.userAgent;
if (useragent.match(/MicroMessenger/i) == 'MicroMessenger') {
     $.ajax({
        type : "POST",
        url : "gotoJSPayJS.json",
        dataType : "json",
        contentType : 'application/json;charset=UTF-8',
        data : JSON.stringify({orderId:orderId}),
        success : function(res){
            if("success" == res.errCode){
                var obj = res.data;
                if (typeof WeixinJSBridge == "undefined"){
                    jQAlert("請在微信瀏覽器發起支付!");
                }else{
                    WeixinJSBridge.invoke('getBrandWCPayRequest',{  
                        "appId" : obj.appId, //公衆號名稱,由商戶傳入  
                        "timeStamp":obj.timeStamp,//時間戳,自1970年以來的秒數  
                        "nonceStr" : obj.nonceStr,//隨機串  
                        "package" : obj.packageName,//商品包信息  
                        "signType" : obj.signType,//微信簽名方式
                        "paySign" : obj.paySign//微信簽名  
                        },function(res){      
                            if(res.err_msg == "get_brand_wcpay_request:ok"){
                                window.location.href="payComplete.do?orderId="+orderId;
                            }else if(res.err_msg == "get_brand_wcpay_request:cancel"){
                                jQAlert("支付取消!");
                            }else if(res.err_msg == "get_brand_wcpay_request:fail"){
                                jQAlert("支付失敗:"+res.err_desc);
                            }else{
                                jQAlert("支付異常");
                            }
                    }); 
                }

            }else{
                jQAlert("支付操作異常,請稍後再試!");
            }

        },
        error : function(XMLHttpRequest, textStatus,
                errorThrown) {
            jQAlert('支付異常,請聯繫客服!');
        },
        complete : function(XMLHttpRequest, textStatus) {
        }
    });

支付返回成功後,幾乎可以保證微信會立刻調用你前面設置的notifyUrl配置的回調地址,接口裏你就需要實現業務邏輯的方法比如更新訂單狀態,修改支付記錄的狀態,變更商品的數量等等業務邏輯,爲了細說這個PayCallback我也列出我的部分代碼:

   /**
     * 支付完成回調函數
     * @param request
     * @return
     */
    @RequestMapping(value="payNotify.do")
    public void wechatPayNotify(HttpServletRequest request,HttpServletResponse resp){
        log.info("++++++進入支付回調界面+++++");
        try {
            log.info("++++++訂單:" + orderId + ",完成回調 :" + result +(("SUCCESS").equals(result)?"":",結果描述:"+resultDes));
        } catch (Exception e) {
            e.printStackTrace();
        }

        //處理完成需要返回微信已經支付完成回覆
        try {
            resp.getWriter().write(XMLBeanUtils.objectToXMLStr(new PayCallback()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

再看下PayCallback對象:

package com.wtp.wechat.bean;
/**
 * 
 * @ClassName: PayCallback 
 * @Description: 回調結束返回微信內容
 * @author tianpengw 
 * @date 2017年10月12日 下午4:06:27 
 *
 */
public class PayCallback {

    private String return_code;
    private String return_msg;

    public PayCallback() {
        this.return_code = "SUCCESS";
        this.return_msg = "OK";
    }

    public String getReturn_code() {
        return return_code;
    }

    public void setReturn_code(String return_code) {
        this.return_code = return_code;
    }

    public String getReturn_msg() {
        return return_msg;
    }

    public void setReturn_msg(String return_msg) {
        this.return_msg = return_msg;
    }
}

三、特殊情況-微信瀏覽器訪問網頁之公衆號支付(補充)

有這樣一種場景,某個商品在朋友圈或通過其他朋友分享,用戶在進入商品詳情裏購買時將使用到的支付方式就是公衆號支付(從API裏也能體現出這裏不支持H5支付),此時作爲服務端的我們,需要的是獲得用戶的openid,由於我們並不能確定該用戶是否是關注用戶,所以這裏我們需要通過網頁授權獲得當前用戶的微信信息(至少需得到openid)

本人採用的是靜默授權的方式
部分代碼片段:

if(HttpHelper.isWechatBrowser(req)){
            /*
             * 靜默授權獲得openid,這裏不查詢的原因是防止已經綁定過公衆號的手機號在其他手機號的微信上進行支付操作,這裏只完成支付操作不進行信息的變更
             */
            String code = req.getParameter("code");
            if(!MyStringUtils.isEmpty(code)){//認證後回調
                Oauth2AccessToken oat = WechatUtil.getUserOpenId(code);
                if(null != oat && !MyStringUtils.isEmpty(oat.getOpenid())){
                    String openId = (String) HttpUtils.getObjectFromSession(req, HttpUtils.openid_name);
                    if(!oat.getOpenid().equals(openId)){
                        mv.addObject("openId", oat.getOpenid());
                    }
                }
            }else{
                //此處開始進行微信認證
                String url = WechatUtil.getOauth2Url(WechatUtil.scope_base, URLEncoder.encode(MyConstants.product_address + "/phone/checkStand.do?orderId="+orderId,"utf-8"));
                mv.setViewName("redirect:"+url);
            }
        }

這樣通過靜默授權獲得當前微信在服務號裏的唯一的openId,然後完成此後的支付操作,

注:可能網友會想這種情況下手機號雖已經綁定了自己的微信openId,但在其他微信裏登陸自己的手機號也是能完成支付的。是的,這裏我的控制邏輯如此,賬號唯一綁定的是用戶的手機號,微信主要作爲輔助支付和自動登陸,當然你可以有自己的的業務邏輯控制,按照自己實際需求來實現。

至此整個普通商戶的公衆號支付已經寫完。

如果感興趣請關注另一個博客【微信支付之H5支付】,最後會把我總結出的微信相關的一些接口調用封裝起來,做成jar包發出來,以後在微信大版本不變的情況下 只需引用jar裏的方法即可,省去不少開發時間。

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