微信公衆號支付

做支付的前提條件:

1,得到用戶的openid,沒獲取到的朋友可以看我的上一篇文章:https://blog.csdn.net/qq_24800377/article/details/53437040

2,有一個微信支付的商戶號,其中公衆號支付功能必須開通。

下面開始具體的支付開發步驟:

步驟:

第一步,訪問微信提供的一個網址,獲取 prepay_id   ,生成統一訂單,它又叫預支付id。

第二步,在需要支付的頁面,調用微信的jssdk,也就是網頁中的javascript,微信有代碼,調起微信支付頁面。(注:prepay_id爲該步驟使用  )

第三步:在微信提供的javascript代碼中填寫支付成功頁面的url,搞定。

看起來就三步,十分簡單,但微信支付能這麼簡單嘛?當然不可能,我做的時候最麻煩的地方就是第一步和第二步中,微信需要的參數,各種亂七八糟的,還有簽名什麼的,還有微信後臺需要配置的一些信息…

第一步詳解:

邏輯:訪問一個網址,按微信要求填寫參數,訪問成功後,微信返回一段xml數據,其中就包括  prepay_id  數據,也是我下一步需要的。    

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

參數:一段xml數據

生成參數xml的代碼:

public static String createXml(WxPayModel wxPayModel) {
    String xml = "<xml>" + "<appid>APPID</appid>" + "<body>BODY</body>" + "<device_info>DEVICEINFO</device_info>"
            + "<mch_id>MCHID</mch_id>" + "<nonce_str>NONCESTR</nonce_str>" + "<notify_url>NOTIFYURL</notify_url>"
            + "<openid>OPENID</openid>" + "<out_trade_no>OUTTRADENO</out_trade_no>" + "<sign>SIGN</sign>"
            + "<total_fee>TOTALFEE</total_fee>" + "<trade_type>TRADETYPE</trade_type>" + "</xml>";

    xml = xml.replace("APPID", WxPayConstant.appid);
    xml = xml.replace("BODY", wxPayModel.getBody());
    xml = xml.replace("DEVICEINFO", WxPayConstant.device_info);
    xml = xml.replace("MCHID", WxPayConstant.mch_id);
    xml = xml.replace("NONCESTR", wxPayModel.getNonceStr());
    xml = xml.replace("NOTIFYURL", WxPayConstant.notify_url);
    xml = xml.replace("OPENID", wxPayModel.getOpenid());
    xml = xml.replace("OUTTRADENO", wxPayModel.getOutTradeNo());
    xml = xml.replace("SIGN", wxPayModel.getSign());
    xml = xml.replace("TOTALFEE", wxPayModel.getTotalFee());
    xml = xml.replace("TRADETYPE", WxPayConstant.trade_type);

    return xml;
}

生成xml所需參數與獲取方法:

appid ==應用ID==登陸微信公衆號後臺-開發-基本配置   查看

body==商品描述==商品或支付單簡要描述  隨便寫,測試時最好上英文,防止編碼錯誤

device_info==設備號==終端設備號(門店號或收銀設備ID),注意:PC網頁或公衆號內支付請傳"WEB"

mch_id == 微信支付商戶號==登陸微信支付後臺,即可看到

nonce_str==隨機字符串==隨機字符串 ,下面有工具方法

notify_url==通知地址==接收微信支付異步通知回調地址,先隨便起一個

openid==用戶標識,前提條件已經獲取到了

out_trade_no==商戶訂單號==商戶系統內部的訂單號,32個字符內、可包含字母,下方有工具方法

sign==簽名==官方給的簽名算法 ,下方有工具方法,注:生成簽名的參數要按照工具方法中的參數寫,它與生成的xml是配套的,如果不一致會一直報簽名失敗,內部的參數可以自定義,但需要與xml同步修改,不建議改。

trade_type==交易類型==JSAPI 寫死

total_fee==總金額==訂單總金額,單位爲分,測試給1即可,1分錢

注:生成簽名sign的時候,需要值key,它是微信商戶平臺(pay.weixin.qq.com)-->賬戶設置-->API安全-->密鑰設置,自定義生成,用MD5加密成32位的字符串

工具方法:

/**
 * 生成隨機數
 *
 * @return
 */
public static String getNonceStr() {
    Random random = new Random();
    return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8");
}
/**
 * 生成訂單id
 *
 * @return
 */
public static String generateUUID() {
    return IdGen.uuid().toString().replaceAll("-", "").substring(0, 32);
}
/**
     * 生成簽名
     * @param wxPayModel
     * @return
     */
    public static String createSign(WxPayModel wxPayModel) {

        // 微信api提供的參數
        String appid = WxPayConstant.appid;
        String mch_id = WxPayConstant.mch_id;
        String device_info = WxPayConstant.device_info;
        String nonce_str = wxPayModel.getNonceStr();
        String notify_url = WxPayConstant.notify_url;

//    System.out.println("++++++++++++++++++++++++++++++++++++++++");
//
//    System.out.println("參與簽名appid:" + appid);
//    System.out.println("參與簽名mch_id:" + mch_id);
//    System.out.println("參與簽名nonce_str:" + nonce_str);
//    System.out.println("參與簽名body:" + wxPayModel.getBody());
//    System.out.println("參與簽名out_trade_no:" + wxPayModel.getOutTradeNo());
//    System.out.println("參與簽名total_fee:" + wxPayModel.getTotalFee());
//    System.out.println("參與簽名spbill_create_ip:" + wxPayModel.getSpbillCreateIp());
//    System.out.println("參與簽名notify_url:" + notify_url);
//    System.out.println("參與簽名device_info:" + device_info);
//    System.out.println("參與簽名trade_type:" + WxPayConstant.trade_type);
//    System.out.println("參與簽名openid:" + wxPayModel.getOpenid());
//
//    System.out.println("++++++++++++++++++++++++++++++++++++++++");

        SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
        parameters.put("appid", appid);
        parameters.put("mch_id", mch_id);
        parameters.put("nonce_str", nonce_str);
        parameters.put("body", wxPayModel.getBody());
        parameters.put("out_trade_no", wxPayModel.getOutTradeNo());
        parameters.put("total_fee", wxPayModel.getTotalFee());
        parameters.put("notify_url", notify_url);
        parameters.put("device_info", device_info);
        parameters.put("trade_type", WxPayConstant.trade_type);
        parameters.put("openid", wxPayModel.getOpenid());

        String characterEncoding = "UTF-8";

        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();// 所有參與傳參的參數按照accsii排序(升序)
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + WxPayConstant.key);

        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

生成xml(reqBody)後,執行以下訪問微信網址的代碼:

URL httpUrl = new URL("https://api.mch.weixin.qq.com/pay/unifiedorder");
  HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
      httpURLConnection.setRequestProperty("Host", "api.mch.weixin.qq.com");
      httpURLConnection.setDoOutput(true);
      httpURLConnection.setRequestMethod("POST");
      httpURLConnection.setConnectTimeout(10*1000);
      httpURLConnection.setReadTimeout(10*1000);
      httpURLConnection.connect();
  OutputStream outputStream = httpURLConnection.getOutputStream();
      outputStream.write(reqBody.getBytes("UTF8"));//reqBody是上面的費大力生成的xml

  //獲取內容
  InputStream inputStream = httpURLConnection.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF8"));
  final StringBuffer stringBuffer = new StringBuffer();
  String line = null;
      while ((line = bufferedReader.readLine()) != null) {
      stringBuffer.append(line);
  }
  String resp = stringBuffer.toString();

      if (stringBuffer!=null) {
      try {
          bufferedReader.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
      if (inputStream!=null) {
      try {
          inputStream.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
      if (outputStream!=null) {
      try {
          outputStream.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

      System.out.println("++++++++++++++++++++++");
      System.out.println("獲取預支付id返回的結果爲:"+resp);
      System.out.println("++++++++++++++++++++++");

  Map<String, String> hm;
  String prepay_id = "";

try {
      hm = WXPayUtil.xmlToMap(resp);

      System.out.println("++++++++++++++++++++++");
      System.out.println("支付id:"+hm.get("prepay_id"));
      System.out.println("++++++++++++++++++++++");

      prepay_id = hm.get("prepay_id");
  } catch (Exception e) {
      e.printStackTrace();
  }
得到: prepay_id  後,第一步搞定。  

第二步詳解:

邏輯:調用微信一段js代碼,按微信要求填寫參數,訪問成功後,微信會彈出輸入密碼付款的界面,付款後,微信將進入回調。

特殊:這一步有兩種方式,第一種方式按照微信文檔上的javascript代碼片段調用,將相應的參數替換後即可成功,即(如下):

備註:我是怎麼調用都沒有成功,一直提示WeixinJSBridge未定義,調了老半天后,因爲時間有限,不得不換了第二種方式。

function onBridgeReady() {
       WeixinJSBridge.invoke('getBrandWCPayRequest', {
               "appId" : "${appid}", //公衆號名稱,由商戶傳入     
               "timeStamp" : "${timeStamp}", //時間戳,自1970年以來的秒數     
               "nonceStr" : "${nonceStr}", //隨機串     
               "package" : "${packageValue}",
               "signType" : "MD5", //微信簽名方式:     
               "paySign" : "${sign}" //微信簽名 
   }, function(res) {
           // 使用以下方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回    ok,但並不保證它絕對可靠。 
           if (res.err_msg == "get_brand_wcpay_request:ok") {
               alert("支付成功");
               window.location.href = "${ctx}/parent/user/buyList";
           } else if (res.err_msg == "get_brand_wcpay_request:fail") {
               alert('支付失敗');
           } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
               alert('支付取消');
           } else {
               alert(res.err_msg);
           }
       });
   }

if (typeof ('WeixinJSBridge') == "undefined") {
       if (document.addEventListener) {
           document.addEventListener('WeixinJSBridgeReady', onBridgeReady,
                   false);
       } else if (document.attachEvent) {
           document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
           document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
       }
   } else {
       onBridgeReady();
   }
第二種方式,是根據微信文檔附錄上的javascript代碼片段寫成,如下:

wx.config({
       debug : true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
               appId : "${appid}", // 必填,公衆號的唯一標識
               timestamp : "${timeStamp}", // 必填,生成簽名的時間戳
               nonceStr : "${nonceStr}", // 必填,生成簽名的隨機串
               signature : "${signature}",// 必填,簽名
               jsApiList : ['chooseWXPay']
       // 必填,需要使用的JS接口列表
   });

   // config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
wx.ready(function() {
       wx.chooseWXPay({//微信支付接口,可以先註釋掉,當確定wx.ready執行後再放開

               appId: "${appid}",
               timestamp : "${timeStamp}", // 支付簽名時間戳,注意微信jssdk中的所有使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符
               nonceStr : "${nonceStr}", // 支付簽名隨機串,不長於 32 位
            package : "${packageValue}", // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=\*\*\*)
               signType : "MD5", // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5'
               paySign : "${paySin}", // 支付簽名

               success : function(res) {
                     /* alert("支付成功,來自網頁的消息:"+res.errMsg); */
           if (res.errMsg == "chooseWXPay:ok") {
                        /* alert("支付成功"); */
           } else if (res.errMsg == "chooseWXPay:fail") {
                        /* alert('支付失敗'); */
           } else if (res.errMsg == "chooseWXPay:cancel") {
                        /* alert('支付取消'); */
           } else {
               alert(res.errMsg);
           }
       }
               });
   });

wx.error(function(res) {
       // config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這裏更新簽名。
       alert('配置信息驗證出錯:' + res);
   });

解釋,當調用以上js代碼後,會先走wx.config,當驗證成功後,會走wx.ready,否則會走wx.error。

wx.config中的參數,需要在控制檯中得到,然後傳到前臺頁面的js中使用(注:除了下面四個參數外,還有調用微信支付接口chooseWXPay的參數,最後要將這些參數一起傳給前臺js中,開發階段可以先只傳下面四個,調通後再傳支付接口需要的參數):

appId:寫死的,就是第一步中的appid,注意區分大小寫,這裏的參數爲:appId

timestamp:簽名時間戳,都是小寫!下方面有工具方法

nonceStr:隨即字符串,用第一步的隨即字符

signature:簽名,下面有工具方法

/**
 * 生成時間戳
 *
 * @return
 */
public static String getTimeStamp() {
    return String.valueOf(System.currentTimeMillis() / 1000);
}

簽名的獲取比較複雜:

1,先通過access_token獲取票據

2,通過票據獲取簽名

/**
 * 獲取accessToken
 */
public static String getAccessToken() throws Exception {

    String appid = WxPayConstant.appid;
    String secret = WxPayConstant.secret;

    String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret;

    System.out.println("+++++++++++++++++++++");
    System.out.println("獲取accessTiken的url:"+url);
    System.out.println("+++++++++++++++++++++");

    HttpGet get = HttpClientConnectionManager.getGetMethod(url);
    HttpResponse response = httpclient.execute(get);
    String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
    JSONObject object = JSON.parseObject(jsonStr);

    System.out.println("+++++++++++++++++++++");
    System.out.println("得到的access_token:"+object.toString());
    System.out.println("+++++++++++++++++++++");

    String accessToken = object.getString("access_token");

    return accessToken;
}

通過access_token獲取臨時票據

/**
 * 獲取臨時票據
 *
 * @return
 */
public static String getJsapiTicket() throws Exception {

    String access_token = getAccessToken();
    String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi";

    HttpGet get = HttpClientConnectionManager.getGetMethod(url);
    HttpResponse response = httpclient.execute(get);
    String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
    JSONObject object = JSON.parseObject(jsonStr);

    System.out.println("++++++++++++++");
    System.out.println("臨時票據json:" + object.toString());
    System.out.println("++++++++++++++");

    String ticket = object.getString("ticket");
    String expires_in = object.getString("expires_in");// 有效時間
    String nowTime = object.getString("expires_in");// 當前時間

    return ticket;
}
/**
 * 生成js-sdk簽名算法
 *
 * @param jsapi_ticket 臨時票據
 * @param url 是你前臺頁面的url,也就是寫微信js代碼的那個頁面的地址,
 * @param nonce_str 隨即字符串,用第一步生的的隨即字符
 * @param timestamp 時間戳
 * @return
 */
public static String sign(String jsapi_ticket, String url, String nonce_str, String timestamp) {

    Map<String, String> ret = new HashMap<String, String>();
    String string1;
    String signature = "";

    // 注意這裏參數名必須全部小寫,且必須有序
    string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "timestamp=" + timestamp + "&url=" + url;

    System.out.println("+++++++++++++++++++++++++++++");
    System.out.println("js-sdk簽名算法:" + string1);
    System.out.println("+++++++++++++++++++++++++++++");

    try {
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(string1.getBytes("UTF-8"));
        signature = byteToHex(crypt.digest());
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return signature;
}
備註:關於隨機數和時間戳,我分別只生成了一次,然後所有用到的地方都用的最開始生成的那一個,至於不用統一的值好不好使,沒有測試過,勤勞的小夥伴們可以自己嘗試一下。

調通wx.config後進入wx.ready。

到了這一步就要開始調用微信的支付js,裏面有六個參數,其中四個參數用上面的值就可以,主要是package、paySign這兩個參數。

package:"prepay_id="+prepay_id。記住,package的參數值一定是:"prepay_id="+prepay_id,不要忘記"prepay_id="

paySign:又一個簽名,下面有簽名工具方法。

public static String createPaySin(String packages, String timeStamp, String nonceStr) {

    SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
    parameters.put("appId", WxPayConstant.appid);
    parameters.put("timeStamp", timeStamp);// 時間戳==規則:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2。看完仍是一臉迷茫的,沒關係,我們有工具類。誰知道呢,直接調用就好了
    parameters.put("nonceStr", nonceStr);
    parameters.put("package", packages);
    parameters.put("signType", "MD5");

    System.out.println("++++++++++++++++");
    System.out.println("第二個簽名appId:"+WxPayConstant.appid);
    System.out.println("第二個簽名timeStamp:"+timeStamp);
    System.out.println("第二個簽名nonceStr:"+nonceStr);
    System.out.println("第二個簽名package:"+packages);
    System.out.println("第二個簽名signType:MD5");
    System.out.println("++++++++++++++++");

    String characterEncoding = "UTF-8";

    StringBuffer sb = new StringBuffer();
    Set es = parameters.entrySet();// 所有參與傳參的參數按照accsii排序(升序)
    Iterator it = es.iterator();
    while (it.hasNext()) {
        Map.Entry entry = (Map.Entry) it.next();
        String k = (String) entry.getKey();
        Object v = entry.getValue();
        if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
            sb.append(k + "=" + v + "&");
        }
    }

    sb.append("key=" + WxPayConstant.key);
    String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();

    System.out.println("++++++++++++++++");
    System.out.println("第二個簽名:"+sign);
    System.out.println("++++++++++++++++");

    return sign;
}

參與簽名的五個參數,到了這步它們應該都有值了,所以直接放進方法內,生成簽名即可。

完成以上所有步驟,這時候可以運行調用嘗試了!!!

如果一次成功,恭喜!!!如果沒成功還得繼續解決…

我遇到的問題:

支付目錄未授權

這是在微信商戶平臺上填寫的目錄授權,實際上就是一個路徑,要具體到支付頁的上一級目錄,比如你的支付頁是a文件夾下的b.jsp,那麼目錄授權必須填到a。

我不是這個原因,這個我填的沒問題,後來發現,我需要將我支付的接口也就是生成prepay_id  所在的接口,當成目錄,填在商戶平臺上,最後終於調用成功。

備註:如果做支付的時候,微信返回不成功,又沒有具體錯誤提示,這時候可以換手機試,手機分安卓和蘋果,安卓手機上沒有提示,不代表蘋果上沒有…我就是安卓手機上沒有,後來在小夥伴的蘋果手機上看到具體錯誤提示的



     

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