JS-SDK

1. 簽名


看到網上的大部分問題都集中在簽名部分,請你們必定請熟讀微信JS-SDK說明文檔附錄5-常見錯誤及解決方法 部分。php

注意html

  • 在計算簽名的過程當中,若是url老是不對請 實驗 首頁的url或 window.location.href。 作到微信拿到真實路徑與咱們拿去生成簽名的路徑是一致的,千萬記住這條前端

  • 前端須要用js獲取當前頁面除去'#'hash部分的連接(可用location.href.split('#')[0]獲取,並且須要encodeURIComponentvue

  • vue每次刷新頁面,都須要從新配置SDK,使用JS_SDK必須先注入配置信息html5

  • 關於html5-History模式在微信瀏覽器內的問題 #481ios

  • IOS:微信IOS版,微信安卓版,每次切換路由,SPA的url是不會變的,發起簽名請求的url參數必須是當前頁面的url就是最初進入頁面時的url(entryUrl.js)git

  • Android:微信安卓版,每次切換路由,SPA的url是會變的,發起簽名請求的url參數必須是當前頁面的url(不是最初進入頁面時的)(entryUrl.js)github

  • 登陸微信公衆平臺進入「公衆號設置」的「功能設置」裏填寫「JS接口安全域名」。ajax

  • 域名格式:若是你的項目域名是http://test.domain.com,那麼JS接口安全域名爲test.domain.comvue-router

  • timestamp: , // 生成簽名的時間戳,精確到秒 秒 秒 秒

  • nonceStr: '', // 必填,生成簽名的隨機串

// entryUrl.js
 全局存儲進入SPA的url(window.entryUrl),Android不變,依舊是獲取當前頁面的url,IOS就使用window.entryUrl
// 記錄進入app的url,後面微信sdk
if (window.entryUrl === '') {
  window.entryUrl = location.href.split('#')[0]
}
// 進行簽名的時候
url: isAndroid() ? location.href.split('#')[0] : window.entryUrl

支付流程圖

payflow.png

2. 簽名 (後臺)

後臺生成簽名後返回給前臺使用,不少微信api須要這個簽名。

生成後能夠驗證一下簽名是否正確 簽名校驗工具

//php
<?php
   namespace Vendor\wxpay;//命名空間
   /**
    * Class wxpay
    * @package Vendor\wxpay
    * @name 用於簽名生產
    * @author weikai
    */
   class wxpay {
       private $appId;
       private $appSecret;

       public function __construct($appId, $appSecret) {
           $this->appId = $appId;
           $this->appSecret = $appSecret;
       }
       //獲取簽名
       public function getSignPackage() {
           $jsapiTicket = $this->getJsApiTicket();//獲取JsApiTicket

           // vue管理路由 url參數建議前臺傳遞
           $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
           // $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";//若是後臺獲取url
           $url = I('get.frontUrl');//獲取前臺傳遞的url
           $timestamp = time();//如今時間戳
           $nonceStr = $this->createNonceStr();//生成隨機字符串

           // 這裏參數的順序要按照 key 值 ASCII 碼升序排序
           $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";

           $signature = sha1($string);//sha1加密排序後的參數生產簽名
        //將全部參數賦值到數組
           $signPackage = array(
               "appId"     => $this->appId,
               "nonceStr"  => $nonceStr,
               "timestamp" => $timestamp,
               "url"       => $url,
               "signature" => $signature,
               "rawString" => $string
           );

           return $signPackage;
       }
     
    //生成隨機字符串
       private function createNonceStr($length = 16) {
           $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
           $str = "";
           for ($i = 0; $i < $length; $i++) {
               $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
           }
           return $str;
       }
    //生成JsApiTicket
       private function getJsApiTicket() {
           // jsapi_ticket 全局存儲與更新
           $data = json_decode(S('jsapi_ticket'));
         //若是緩存中的JsApiTicket 不在有效期內從新生成JsApiTicket 
           if ($data->expire_time < time()) {
               $accessToken = $this->getAccessToken();//獲取AccessToken
               // 若是是企業號用如下 URL 獲取 ticket
               // $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$accessToken";
               $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
               $res = json_decode($this->httpGet($url));
               $ticket = $res->ticket;
             //更新有效期存入緩存
               if ($ticket) {
                   $data->expire_time = time() + 7000;
                   $data->jsapi_ticket = $ticket;
                   S('jsapi_ticket',json_encode($data));
               }
           } else {
             //不然緩存中的JsApiTicket 在有效期內就直接用
               $ticket = $data->jsapi_ticket;
           }

           return $ticket;
       }
    //獲取全局AccessToken
       public function getAccessToken() {
           // access_token 全局存儲與更新
           $data = json_decode(S('access_token'));
           if ($data->expire_time < time()) {
               // 若是是企業號用如下URL獲取access_token
               // $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$this->appId&corpsecret=$this->appSecret";
               $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret";
               $res = json_decode($this->httpGet($url));
               $access_token = $res->access_token;
               if ($access_token) {
                   $data->expire_time = time() + 7000;
                   $data->access_token = $access_token;
                   S('access_token',json_encode($data));
               }
           } else {
               $access_token = $data->access_token;
           }
           return $access_token;
       }

       //curl 請求
       private function httpGet($url) {
           $curl = curl_init();
           curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
           curl_setopt($curl, CURLOPT_TIMEOUT, 500);
           // 爲保證第三方服務器與微信服務器之間數據傳輸的安全性,全部微信接口採用https方式調用,必須使用下面2行代碼打開ssl安全校驗。
           // 若是在部署過程當中代碼在此處驗證失敗,請到 http://curl.haxx.se/ca/cacert.pem 下載新的證書判別文件。
           curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
           curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true);
           curl_setopt($curl, CURLOPT_URL, $url);

           $res = curl_exec($curl);
           curl_close($curl);

           return $res;
       }


   }

3. 簽名(前臺)

  • [x] 微信配置
  • [x] 微信掃一掃
  • [x] 分享到朋友圈
  • [x] 朋友的設置
  • [x] 分享的基本配置
  • [x] 微信支付
/**
 * WECHAT.js
 * Created by  isam2016 
 */

import wx from 'weixin-js-sdk';
import axios from 'axios';

var JsWeChatApis = [
  'checkJsApi',
   請補全列表
];

var isWeChatReady = false;// 檢查微信wx.ready
 
export default class AlWeChat {
  constructor(object) {
    this.object = object;// vue 須要使用vue 解決回調
    this.wxConfig();// 初始微信配置
  }
  /**
   * 微信配置
   * @Author   Hybrid
   * @DateTime 2017-11-21
   */
  wxConfig() {
    let self = this;
    axios.get('/home/OrderConfirm/wxConfig', {
      params: {
        frontUrl: location.href.split('#')[0]// 前臺吧url 傳到後臺 並且須要encodeURIComponent,
      }
    }).then(function(response) {
      var attachment = response.data.data;// 後臺統一調配數據,返回前臺
      // console.log(attachment);
      wx.config({
        debug: false,
        appId: attachment.appId,
        timestamp: attachment.timestamp, // 支付簽名時間戳小寫s 時間戳(timestamp)值要記住精確到秒,不是毫秒。
        nonceStr: attachment.nonceStr,//支付簽名隨機串,不長於 32 位,大寫s
        signature: attachment.signature,
        url: attachment.url,
        jsApiList: JsWeChatApis
      });
      wx.ready(function () {
        isWeChatReady = true;
        self.object && self.wxQDetailShare()//分享到朋友圈+朋友的設置
      });
      wx.error(function (res) {
        //console.log(JSON.stringify(res));
      });

    }).catch(function (error) {
      // console.log(error)
    });
  }

  /**
   * 微信掃一掃
   * @Author   Hybrid
   * @DateTime 2017-11-21
   * @return   {[type]}   [description]
   */
  wxScanQRCode(fn) {
    wx.scanQRCode({
      needResult: 1, // 默認爲0,掃描結果由微信處理,1則直接返回掃描結果,
      scanType: ["qrCode"], // 能夠指定掃二維碼仍是一維碼,默認兩者都有
      success: function (res) {
        var result = res.resultStr; // 當needResult 爲 1 時,掃碼返回的結果
        fn(result);
      }
    });
  }

  /**
   * 分享到朋友圈+朋友的設置
   * 能夠動態設置
   * @Author   Hybrid
   * @DateTime 2017-11-21
   * @param    {}   data [展現數據]
   * @param    {[type]}   eqid [description]
   * @return   {[type]}        [description]
   */
  wxQDetailShare() {
    var config = {
      title: 'XXX',
      desc: 'XXX',
      imgUrl: 'XXX',
      link: 'XXX',
    };
    var shareConfig = {
      message: config,
      timeLine: {
        title: config.title,
        desc: config.desc,
        imgUrl: config.imgUrl,
        link: config.link,
      },
    };
    this.wxShare(shareConfig);
  }

  /**
   * 分享的基本配置
   * @Author   Hybrid
   * @DateTime 2017-11-21
   * @param    {}   shareConfig [不一樣類型的分享有不一樣的配置]
   * @return   {[type]}               [description]
   */
  wxShare(shareConfig) {
    let self = this;
    if (isWeChatReady) {
      /**
       * 分享到朋友圈
       * @Author   Hybrid
       */
      wx.onMenuShareTimeline({
        title: shareConfig.timeLine.title, // 分享標題
        link: shareConfig.timeLine.link, // 分享連接
        imgUrl: shareConfig.timeLine.imgUrl, // 分享圖標
        success: function () {
          self.object.closecovershow();// 回調
          // 用戶確認分享後執行的回調函數
        },
        cancel: function () {
          self.object.closecovershow();
          // 用戶取消分享後執行的回調函數
        }
      });

      /**
       * 分享給朋友
       * @Author   Hybrid
       */
      wx.onMenuShareAppMessage({
        title: shareConfig.message.title, // 分享標題
        desc: shareConfig.message.desc, // 分享描述
        link: shareConfig.message.link, // 分享連接
        imgUrl: shareConfig.message.imgUrl, // 分享圖標  type: '', // 分享類型,music、video或link,不填默認爲link
        type: '', // 分享類型,music、video或link,不填默認爲link
        dataUrl: '', // 若是type是music或video,則要提供數據連接,默認爲空
        success: function () {
          self.object.closecovershow();
          // 用戶確認分享後執行的回調函數
        },
        cancel: function () {
          self.object.closecovershow();
          // 用戶取消分享後執行的回調函數
        }
      });
    }
  }


  /**
   * 微信支付
   * @Author   Hybrid
   * @DateTime 2017-11-21
   * @param    {string}   router 單頁面應用,由前臺通知URL
   * @return   {[type]}          [description]
   */
  payWeChat(order_num) {
    let self = this;
    axios.get('/home/OrderConfirm/orderPay', {
      params: {
        type: 'weixin',
        frontUrl: location.href.split('#')[0],
        order_num// 訂單號
      }
    }).then(function (response) {
      var attachment = response.data.data;// 後臺返回參數
      localStorage.setItem(wechatCode, '');
      //alert(location.href)
      WeixinJSBridge.invoke('getBrandWCPayRequest', {
        "appId": attachment.appId,
        "timeStamp": attachment.timeStamp, // 大寫S
        "nonceStr": attachment.nonceStr, // 大寫S
        "package": attachment.package,
        "signType": 'MD5',
        "paySign": attachment.paySign,
      }, function (res) {
        // console.log(res);
        if (res.err_msg == "get_brand_wcpay_request:ok") {
          self.object.$router.push("/paysuccess");
        } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
          self.object.$router.push("/ordercenter");
        } else {
          // localStorage.setItem(wechatCodeOld, '');
          localStorage.setItem(wechatCode, '');
          //alert("支付失敗!" + JSON.stringify(res) + "當前路徑" + location.href);
          // alert("支付失敗!" + JSON.stringify(res));
          //   resolve(-1);
        }
      })

    }).catch(function (err) {
      console.log(JSON.stringify(err));
    })
  }
}

在前端調用

/**
 * 注入配置
 * this 是 vue
 * 注意: 分享到朋友圈或分享到朋友,每一個頁面都須要配置.因此最好在每一個頁面調用一下 注入配置
 */
var WeChat = new AlWeChat(this) 
WeChat.payWeChat(12345678) // 調用支付
WeChat.wxScanQRCode(fn) // 掃一掃

3.微信支付

  • 支付申請:微信支付 - 公衆號支付 -

  • 微信支付文檔

  • 請仔細閱讀公衆號支付開發步驟

  • 設置支付目錄

  • 設置支付受權目錄注意3點:

    • 全部使用公衆號支付方式發起支付請求的連接地址,都必須在支付受權目錄之下;

    • 最多設置5個支付受權目錄,且域名必須經過ICP備案;

    • 頭部要包含http或https,須細化到二級或三級目錄,以左斜槓「/」結尾。

  • 設置支付受權目錄的具體規則是這樣的:

1

好比:調用支付的頁面地址爲 http://a.b.com/pay/weixin/c.html,`
那麼:受權目錄配置爲 http://a.b.com/pay/weixin/`
2
     
好比:調用支付的頁面地址爲 http://a.b.com/pay/weixin,
那麼:受權目錄配置爲 http://a.b.com/pay/
3

好比:調用支付的頁面地址爲 http://a.b.com/pay,
那麼:受權目錄配置爲 http://a.b.com/
4

好比:調用支付的頁面地址爲  http://a.b.com/pay/weixin/c.html?name=mango,
那麼:受權目錄配置爲  http://a.b.com/pay/weixin/
5(vue spa)

 好比:調用支付的頁面地址爲  http://a.b.com/#/pay/weixin/c.html?name=mango
 那麼:受權目錄配置爲   http://a.b.com/
6(vue spa)

 好比:調用支付的頁面地址爲  http://a.b.com/#!/cart/index(一般咱們會改變URL http://a.b.com/?#!/cart/index)
 那麼:受權目錄配置爲   http://a.b.com/

咱們在網頁受權的時候改變url

location.href = `http://m.example.com/?a=1#${location.href.split('#')[1]}`; // 增長a=1 防止支付錯誤 防止前臺死循環

PHP

咱們基於微信支付官方demo作了優化(微信公衆號支付)

源碼目錄 :

wxpay\wxjsapi.php

使用方法:

1. 將源碼文件放到Thinkphp框架以下目錄內

項目名/ThinkPHP/Library/Vendor/wxpay/wxjsapi.php

2. 微信支付方法

只需導入sdk文件,實例化sdk類 調用getParameters方法傳入訂單數據

/*
  * php
  * @name 微信支付
  * @author weikai
  */
 public function orderPay(){
     $type = I('get.type');
     //微信jsapi 支付
     if($type=='weixin'){
         // 導入微信支付sdk
         Vendor('wxpay.wxjsapi');
         $wxpay=new \Weixinpay();//實例化sdk類
        //獲取訂單數據傳入sdk  getParameters方法中
          $order_num = I('get.order_num');
          $orderData = M('order')->where('order_number='.$order_num)->find();
          if($orderData){
              $data=$wxpay->getParameters($orderData);
          }else{
              return $this->ajaxReturn(show(0,'無此訂單'));
          }
        //最後返回支付簽名信息及預支付id
         if($data){
             return $this->ajaxReturn(show(1,'簽名信息',$data));
         }else{
             return $this->ajaxReturn(show(0,'簽名信息失敗'));
         }
     }
 }
3. 微信SDK getParameters方法配置 (路徑:wxpay\wxjsapi.php內)
/**
    * 獲取jssdk須要用到的數據
    * @return array jssdk須要用到的數據
    */
    public function getParameters($orderData){
           $order=array(
               'body'=>"商品描述".$orderData['order_number'],// 商品描述(須要根據本身的業務修改)
               'total_fee'=>$orderData['total_price']*100,// 訂單金額  以(分)爲單位(須要根據本身的業務修改)
               'out_trade_no'=>$orderData['order_number'],// 訂單號(須要根據本身的業務修改)
               'product_id'=>'10001',// 商品id(須要根據本身的業務修改)
               'trade_type'=>'JSAPI',// JSAPI公衆號支付
               'openid'=>session('openid')// 獲取到的openid
           );
           // 統一下單 獲取prepay_id 具體參照源文件內
           $unified_order=$this->unifiedOrder($order);
           // 獲取當前時間戳
           $time=time();
           // 組合jssdk須要用到的數據
           $config = $this->config;
           $data=array(
               'appId'=>$config['APPID'], //appid
               'timeStamp'=>strval($time), //時間戳
               'nonceStr'=>$this->getNonceStr(32),// 隨機字符串
               'package'=>'prepay_id='.$unified_order['prepay_id'],// 預支付交易會話標識
               'signType'=>'MD5'//加密方式
           );
           // 生成簽名
           $data['paySign']=$this->makeSign($data);
       
           return $data;
       
       }
     }

​ 成功返回信息如圖:

[圖片上傳失敗...(image-5aa7c9-1522549296208)]

返回參數說明:

appId:微信公衆號標識

nonceStr:隨機字符串

prepay_id:微信生成的預支付會話標識,用於後續接口調用中使用,該值有效期爲2小時

paySign:支付簽名

signType:簽名類型

timeStamp:時間戳

4.前端處理支付
在 WECHAT.js 中paywechat方法

5.支付結果通知

/**
     * 微信公衆號支付回調驗證
     * @return array 返回數組格式的notify數據
     */
    public function notify(){
         Vendor('wxpay.wxjsapi');//引入sdk
         $wxpay=new \Weixinpay();//實例化sdk類
        // 獲取微信支付通知的xml數據
        $xml=file_get_contents('php://input', 'r');
        
        // 轉成php數組
        $data=toArray($xml);
        // 保存原sign
        $data_sign=$data['sign'];
        // sign不參與簽名
        unset($data['sign']);
        $sign=$wxpay->makeSign($data);
        // 判斷簽名是否正確  判斷支付狀態
        if ($sign===$data_sign && $data['return_code']=='SUCCESS' && $data['result_code']=='SUCCESS') {
            $result=$data;
            //將支付狀態更新進訂單表
           //其餘業務代碼
        }else{
            $result=false;
        }
        // 返回狀態給微信服務器
        if ($result) {
            $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';

        }else{
            $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名失敗]]></return_msg></xml>';
        }
        echo $str;
        return $result;
    }

toArray -XML轉換數組的函數

/**
 * php
 * 將xml轉爲array
 * @param  string $xml xml字符串
 * @return array       轉換獲得的數組
 */
function toArray($xml){   
  //禁止引用外部xml實體
  libxml_disable_entity_loader(true);
  $result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);        
  return $result;
}

6.前臺輪詢判斷訂單支付狀態,成功給用戶提示。

相關文章
相關標籤/搜索