PHP,JS開發微信公衆號JSAPI支付遇到的那些坑

在之前,寫了一下微信公衆號支付,現在和大家分享一下經驗。

  1. 首先,在開發之前,看一下官方的開發文檔:https://pay.weixin.qq.com/wiki/doc/api/index.html 點擊選擇JSAPI支付

  2. 然後,看一下商戶平臺的配置信息,微信商戶平臺地址:
    https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F
    (1)登錄之後,點擊產品中心,點擊公衆號支付,看看是否有公衆號支付(沒有的,請去申請一下)。
    (2)接下來,開始配置一些信息。
    第一坑
    請確保實際支付時的請求目錄與後臺配置的目錄一致,否則將無法成功喚起微信支付。進行配置支付授權目錄:也就是你的支付頁面所在的目錄,一定是生產環境的,微信不支持 ip +端口 形式的地址 異步通知也不支持,所以測試都需要線上真實環境的域名+支付頁面所在目錄。
    圖片…
    然後,還需要你登錄微信公衆號平臺配置JS接口安全域名。
    登錄網址:https://mp.weixin.qq.com
    第二坑
    所謂安全域名,就是你線上服務器上的備案域名,記住一定要提前備案,或者找已經備過案的服務器,進行配置域名和上面提到的JSAPI支付支付目錄。
    圖片…

  3. 配置完基本信息之後。
    接着,我們需要獲取必傳參數:appid,mch_id,加密key
    appid: 在微信公衆號平臺,找到基本配置,名字叫開發者ID(AppID)。
    mch_id:這個東西是你登錄商戶平臺的商戶號。去商戶平臺的個人信息裏找就行了。
    key: 微信商戶平臺(pay.weixin.qq.com)–>賬戶設置–>API安全–>密鑰設置。
    圖片…

  4. 再說一下其他的,統一下單必傳參數:
    隨機字符串:nonce_str,只要不重複就行。
    簽名: sign,微信有專門的簽名算法
    https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
    商品描述:body,主要是說,你開發公衆號支付,是幹什麼的。
    商戶訂單號:out_trade_no ,這個,在後臺生成一個唯一的訂單號就行。
    如PHP的time(),uniqid()。
    標價金額:total_fee,注意這個數字,單位是
    終端IP:spbill_create_ip,獲取用戶的終端IP
    通知地址:notify_url,異步接收微信支付結果通知的回調地址,通知url必須爲外網可訪問的url,不能攜帶參數。
    交易類型:trade_type,公衆號支付,選擇——>JSAPI -JSAPI支付
    第三坑
    用戶標識:openid,“trade_type=JSAPI時(即JSAPI支付),此參數必傳,此參數爲微信用戶在商戶對應appid下的唯一標識”。(我當時的處理是,在用戶剛進入頁面的時候,拿‘code’,向微信發送請求,獲取openid,沒有code,就要先獲取code。我在下邊代碼中會說。)
    參考獲取openid的文檔:https://mp.weixin.qq.com/wikit=resource/res_main&id=mp1421140842
    現在,所有的必傳參數,我們都有了。
    開始上代碼:
    後臺:我當時寫了倆文件。一個配置文件(WeiXinPayConfig):主要是一些上面提到的參數。一個是業務邏輯文件,處理用戶下單的操作。
    配置文件(WeiXinPayConfig.php):

const APPID             = '';     //微信公衆號appid
const SECRET            = '';     //AppSecret
const MCH_ID            = '';     //微信支付商戶號
const KEY               = '';     //自己設置的微信key 
const NOTIFY_URL        = '';     //異步回調地址,需外網可以訪問
const TOKEN             = '';     // Token
//config中的log參數
const LEVEL             = '';   
const FILE              = '';
//config中的oauth的參數
const CALLBACK          = '';     //微信公衆號appid
//config中的payment的參數
const CERT_PATH         = '';     //證書路徑設置,絕對路徑
const KEY_PATH          = '';     //密匙文件,絕對路徑
//attributes中的參數
const BODY              = '';     //支付後的支付訂單信息
const TRADE_TYPE        = 'JSAPI';//喚醒方式
//wcPayParams中的參數
const SIGN_TYPE         = ''      //微信簽名方式

上面有些參數你可能比較疑惑,那請你看一下’EasyWeChat’:
https://www.easywechat.com/docs/master/overview
業務邏輯文件(WechatPayController.php):

<?php

namespace App\Http\Controllers\Alipay;

use App\Http\Controllers\Controller;
use App\Model\WeChatPayDatabase;
use config\WeiXinPayConfig;
class WechatPayController  extends Controller
{
    /**微信 支付 下單付款
     * @var null
     */
    protected  $app = null;
    function getPay(Request $request)
    {
        $total_fee    = 1000;                                          //付款金額,單位爲分
        $out_trade_no = time();                                        //平臺內部訂單號
        $phone        = $request->phone;                               //學生電話
        $pay_ways     = $request->pay_ways;                            //學生的支付方式
        $student_id   = $request->student_id;                          //學生學號
        $key          = WeiXinPayConfig::KEY;                          //自己設置的微信key
        $appid        = WeiXinPayConfig::APPID;                        //微信公衆號appid
        $secret       = WeiXinPayConfig::SECRET;                       //AppSecret
        $mch_id       = WeiXinPayConfig::MCH_ID;                       //微信支付商戶號
        $notify_url   = WeiXinPayConfig::NOTIFY_URL;                   //異步回調地址,需外網可以訪問
        $config       = [
           'debug'    => true,                                         // 調試設置爲開啓
           'app_id'   => $appid,                                       // AppID
           'secret'   => $secret,                                      // AppSecret
           'token'    => WeiXinPayConfig::TOKEN,                       // Token
           'aes_key'  => '',                                           // EncodingAESKey,安全模式下請一定要填寫!!!
           'log'      => [                                             //日誌
               'level'        => WeiXinPayConfig::LEVEL,
               'permission'   => 0777,
               'file'         => WeiXinPayConfig::FILE,
           ],
           'oauth'   => [                                              //網頁授權,獲取用戶信息
               'scopes'       => ['snsapi_userinfo'],
               'callback'     => WeiXinPayConfig::CALLBACK,
           ],
           'payment' => [
               'merchant_id'  => $mch_id,                              //微信支付商戶號
               'key'          => $key,                                 //自己設置的微信key
               'cert_path'    => WeiXinPayConfig::CERT_PATH,           //證書路徑設置
               'key_path'     => WeiXinPayConfig::KEY_PATH,            //密匙文件
               'notify_url'   => $notify_url,                          //異步回調地址,需外網可以訪問
           ],
        ];
        $this->app        = new Application($config);
        $payment          = $this->app->payment;
        $insert_id        = WeChatPayDatabase::insertstuorder($student_id,$phone,$out_trade_no,$pay_ways); //把訂單信息存入數據
        if($insert_id){                                                //如果訂單存入成功
            $attributes        = [
                'body'         => WeiXinPayConfig::BODY,               //支付後的支付訂單信息
                'total_fee'    => $total_fee,                          //支付金額,以'分'爲單位
                'notify_url'   => $notify_url,                         //回調函數
                'out_trade_no' => $out_trade_no,                       //訂單號
                'trade_type'   => WeiXinPayConfig::TRADE_TYPE,         //喚醒接口
                'openid'       => session('openId'),                   //從session中取出用戶的openid
                'spbill_create_ip'=>$this->getIP()                     //獲取用戶端口號
            ];
            $order          = new Order($attributes);
            $result         = $payment->prepare($order);               //使用接口完成訂單的創建
            $wcPayParams    = [
                "appId"     => $appid,
                "timeStamp" => time(),
                "nonceStr"  => $result['nonce_str'],                   //隨機串
                "package"   => "prepay_id=".$result['prepay_id'],      //訂單ID
                "signType"  => WeiXinPayConfig::SIGN_TYPE              //微信簽名方式
            ];
            $paySign        = $this->MakeSign($wcPayParams);           //生成簽名
            $wcPayParams['paySign'] = $paySign;                        //存簽名
            $wcPayParams['payId']   = $insert_id;                      //存數據庫中的訂單ID
            return $this->responseToJson(0,'下單成功',$wcPayParams);
        }else{
            return $this->responseToJson(1,'下單失敗');
        }
    }
    /**修改訂單狀態
     * @param Request $request
     */
    function updateOrder(Request $request)
    {
         WeChatPayDatabase::updateOrders($request->id);
    }

    /**數據返回JSON格式
     * @param int $code
     * @param string $msg
     * @param null $paras
     * @return \Illuminate\Http\JsonResponse
     */
    //所有返回前臺的數據,封裝成JSON數據格式
    function responseToJson($code = 0, $msg = '', $paras = null) {
        $res["code"] = $code;
        $res["msg"] = $msg;
        $res["result"] = $paras;
        return response()->json($res);
    }

    /**生成微信簽名
     * @param $sign
     * @return string
     */
    //微信支付簽名
    public function MakeSign($sign){
        //簽名步驟一:按字典序排序參數
		ksort($sign);
		$string = $this->ToUrlParams($sign);
		//簽名步驟二:在string後加入KEY
		$string = $string . "&key=你自己的KEY";
		//簽名步驟三:MD5加密
		$string = md5($string);
		//簽名步驟四:所有字符轉爲大寫
		$result = strtoupper($string);
		return $result;
	}

    /**解析生成簽名時傳來的JSON數據
     * @param $sign
     * @return string
     */
    //把微信支付簽名,封裝拼接算法
    public function ToUrlParams($sign){
		$buff = "";
		foreach ($sign as $k => $v)
		{
			if($k != "sign" && $v != "" && !is_array($v)){
				$buff .= $k . "=" . $v . "&";
			}
		}
		$buff = trim($buff, "&");
		return $buff;
	 }

    /**獲取終端IP
     * @return array|false|string
     */
    //獲取用戶的終端IP
    function getIP() {
        if (getenv("HTTP_CLIENT_IP"))            //取得用戶的IP代碼;
            $ip = getenv("HTTP_CLIENT_IP");
        else if(getenv("HTTP_X_FORWARDED_FOR"))  //透過代理服務器取得客戶端的真實 IP 地址
            $ip = getenv("HTTP_X_FORWARDED_FOR");
        else if(getenv("REMOTE_ADDR"))           //正在瀏覽當前頁面用戶的IP 地址。
            $ip = getenv("REMOTE_ADDR");
        else $ip = "Unknow";
        return $ip;
    }

    /**
     * @param Request $request
     * @return
     */
    //目的:獲取用戶的openid,首先獲取用戶的code,然後用code換取openid。
    public function index(Request $request){
        if (strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false) { //如果是微信瀏覽器
            if($request->get('code')){                                         //如果有code參數
                $code=$request->get('code');
                $get_token_url="https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx2fffc402a50e03a5&secret=956397f1970f6d1b114a8ac835bc0a77&code=".$code."&grant_type=authorization_code";
                $ch = curl_init();
                curl_setopt($ch,CURLOPT_URL,$get_token_url);
                curl_setopt($ch,CURLOPT_HEADER,0);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );
                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 100);
                $openid = curl_exec($ch);                                      //拿code換區opeid
                $Id=json_decode($openid);
                session(['openId' => $Id->openid]);                          //存session
                curl_close($ch);
            }else{                        //沒有code就先 跳轉 然後回調到這裏 執行上面的if獲取Openid
                return redirect("https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx2fffc402a50e03a5&redirect_uri=你自己的獲取'openid的路由'&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect");
            }
        }
        return redirect('/');
    }

    /**支付後回調
     * @return mixed
     */
    public function wechatNotify() {
            $response =$this->app->payment->handleNotify(function($notify, $successful){
            $out_no = $notify->out_trade_no;
            // 使用通知裏的 "微信支付訂單號" 或者 "商戶訂單號" 去自己的數據庫找到訂單
            $order = WeChatPayDatabase::sestuordernum($out_no);
            if (!$order) {                             // 如果訂單不存在
                return 'Order not exist.';             // 告訴微信,我已經處理完了,訂單沒找到,別再通知我了
            }
            if ($successful) {                         // 用戶是否支付成功
                WeChatPayDatabase::updateorstatus($out_no);
                return true;
            } else {                                   // 用戶支付失敗
                return false;
            }
            return true;
        });
        return $response;
    }
}

前臺:可以參考官方文檔,微信內H5調起支付:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
我當時用的是VUE。

//向後臺發送,進行下單
postpay(){
                let self = this;
                this.$http.post('wechatpay/getpay',{
                    student_id : this.form.student_id,
                    phone      : this.form.phone,
                    pay_ways   : this.pay_ways,
                }).then(
                    function (response) {
                        let data = response.data;
                        if(data.code == 0){
                            self.sicallpay(data.result);
                        }
                        else {
                            self.$message({
                                showClose: true,
                                message: data.msg,
                                type: 'error'
                            });
                        }
                    })
            },
            //
            sicallpay(result){
                if(typeof WeixinJSBridge == 'undefined'){   //WeixinJSBridge內置對象在其他瀏覽器中無效。
                    if(document.addEventListener()){
                        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                    }else if(document.attachEvent) {
                        document.attachEvent('WeixinJSBridgeReady',  onBridgeReady);
                        document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
                    }
                }else {
                    this.onBridgeReady(result);
                }
            },
            onBridgeReady(result){
                let self = this;
                WeixinJSBridge.invoke(
                    'getBrandWCPayRequest', {
                        "appId":result.appId,         //公衆號名稱,由商戶傳入   
                        "timeStamp":result.timeStamp, //時間戳,自1970年以來的秒數
                        "nonceStr":result.nonceStr,   //隨機串   
                        "package":result.package,
                        "signType":"MD5",             //微信簽名方式: 
                        "paySign":result.paySign
                    },
                    function (res) {
                        /**
                        JS API的返回結果get_brand_wcpay_request:ok僅在用戶成功完成支付時返回。
                        *具體的其他返回結果,參考官方文檔
                        */
                        if (res.err_msg == "get_brand_wcpay_request:ok") {
                            self.updateOrders(result.payId);
                        } else {
                            self.$message({
                                showClose: true,
                                message: '支付失敗,無法報名',
                                type: 'error'
                            });
                        }
                    }
                );
            },
            updateOrders(orderid){
           /**
             * 這裏寫你自己的業務邏輯
             */
            },
        }

第四坑
前臺喚醒微信支付,需要注意,要兼容IOS系統。上面的前臺寫的,只適用於android。
兼容蘋果系統,我後面會補上。
到這裏,微信公衆號JSAPI支付,基本已經完成了。
喜歡,請留言,點贊!!!!!!

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