微信H5支付

微信H5支付是指商戶在微信客戶端外的移動端網(wǎng)頁展示商品或服務(wù)止潘,用戶在商品展示頁確認(rèn)使用微信支付時譬嚣,商戶發(fā)起本服務(wù)喚起微信客戶端進(jìn)行支付。主要用于觸屏版的手機(jī)瀏覽器請求微信支付的場景秩冈,可以方便的從外部瀏覽器喚醒微信支付租漂。

體驗(yàn)頁面:https://wxpay.wxutil.com/mch/pay/h5.v2.php

支付流程

  1. 用戶在商品頁完成下單并使用微信支付進(jìn)行支付
  2. 由商戶后臺向微信支付發(fā)起下單請求即調(diào)用統(tǒng)一下單接口
  3. 統(tǒng)一下單接口返回支付相關(guān)參數(shù)給商戶后臺,商戶通過專用鏈接調(diào)起微信支付中間頁面。
  4. 在中間頁進(jìn)行H5權(quán)限校驗(yàn)以及安全性檢測
  5. 若支付成功商戶后臺會收到微信端的異步通知
  6. 用戶在微信支付收銀平臺完成支付或取消支付并返回商品頁面
  7. 商品在展示頁引導(dǎo)用戶主動發(fā)起支付結(jié)果的查詢
  8. 商戶后臺判斷是否 接收到微信端的支付結(jié)果通知岳瞭,若沒有后臺調(diào)用訂單查詢接口確定訂單狀態(tài)。
  9. 展示最終的訂單支付結(jié)果給用戶
支付流程

開發(fā)流程

  1. 用戶下單時選擇微信支付
  2. 商戶進(jìn)行業(yè)務(wù)邏輯處理并調(diào)用微信統(tǒng)一下單接口烛恤,微信H5交易類型為trade_type=MWEB粱锐。
  3. 調(diào)用下單接口成功時,微信會返回包含支付跳轉(zhuǎn)URL等相關(guān)參數(shù),商戶通過參數(shù)mweb_url調(diào)起支付中間頁。
  4. 在中間頁微信會進(jìn)行H5權(quán)限的校驗(yàn)
  5. 支付成功,微信會向商戶發(fā)送異步結(jié)果通知。

統(tǒng)一下單接口

<?php
class Wechat
{
    /**
      * 組建簽名
      * @param array $params 請求參數(shù)
      * @param string $key 秘鑰
      */
    public static function getSign($params, $key)
    {
        foreach ($params as $k=>$v) {
            if (!$v) {
                unset($params[$k]);
            }
        }
        ksort($params);
        $paramStr = '';
        foreach ($params as $k => $v) {
            $paramStr = $paramStr . $k . '=' . $v . '&';
        }
        $paramStr = $paramStr . 'key='.$key;
        $sign = strtoupper(md5($paramStr));
        return $sign;
    }

    /**
      * 將數(shù)組轉(zhuǎn)為XML
      * @param array $params 支付請求參數(shù)
      */
    public static function getXml($params)
    {
        if(!is_array($params)|| count($params) <= 0) {
            return false;
        }
        $xml = "<xml>";
        foreach ($params as $key=>$val) {
            if (is_numeric($val)) {
                $xml.="<".$key.">".$val."</".$key.">";
            } else {
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    /*官方統(tǒng)一下單*/
    public static function unifiedOrder($appid, $mch_id, $key, $total_fee, $out_trade_no, $body, $notify_url, $wap_url)
    {
        $spbill_create_ip = $_SERVER["REMOTE_ADDR"]; //獲得用戶設(shè)備IP
        $nonce_str=MD5($out_trade_no);//隨機(jī)字符串
        $trade_type = 'MWEB';//交易類型 微信H5交易

        $scene_info ='{"h5_info":{"type":"Wap","wap_url":"'.$wap_url.'","wap_name":"支付"}}';//場景信息 必要參數(shù)

        $signA ="appid=$appid&body=$body&mch_id=$mch_id&nonce_str=$nonce_str&notify_url=$notify_url&out_trade_no=$out_trade_no&scene_info=$scene_info&spbill_create_ip=$spbill_create_ip&total_fee=$total_fee&trade_type=$trade_type";
        //拼接字符串 注意順序微信有個測試網(wǎng)址 順序按照他的來 直接點(diǎn)下面的校正測試 包括下面XML 是否正確
        $strSignTmp = $signA."&key=$key";
        // MD5 后轉(zhuǎn)換成大寫
        $sign = strtoupper(MD5($strSignTmp));
        //拼接成XML格式 *XML格式文件要求非常嚴(yán)謹(jǐn)不能有空格這點(diǎn)一定要注意
        $post_data="<xml><appid>$appid</appid><body>$body</body><mch_id>$mch_id</mch_id><nonce_str>$nonce_str</nonce_str><notify_url>$notify_url</notify_url><out_trade_no>$out_trade_no</out_trade_no><scene_info>$scene_info</scene_info><spbill_create_ip>$spbill_create_ip</spbill_create_ip><total_fee>$total_fee</total_fee><trade_type>$trade_type</trade_type><sign>$sign</sign>
</xml>";


        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信下單接口連接不用更改

        $headers = array();
        $headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
        $headers[] = 'Connection: Keep-Alive';
        $headers[] = 'Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3';
        $headers[] = 'Accept-Encoding: gzip, deflate';
        $headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20100101 Firefox/22.0';

        $dataxml = self::httpPost($url,$post_data,$headers);//傳參調(diào)用curl請求
        $objectxml = (array)simplexml_load_string($dataxml,'SimpleXMLElement',LIBXML_NOCDATA); //將微信返回的XML 轉(zhuǎn)換成數(shù)組
        //支付跳轉(zhuǎn)URL
        $mweb_url = "";
        if($objectxml['return_code'] == 'SUCCESS'){
            $mweb_url= $objectxml['mweb_url'];
        }

        return $mweb_url;//跳轉(zhuǎn)后臺獲取的支付連接
    }
    /**
     * 獲取微信支付中間頁deepLink參數(shù)
     * @param string $url 微信返回的mweb_url
     * @param string $ip 用戶端IP
     */
    public static function getDeeplink($url, $ip)
    {
        $headers = array("X-FORWARDED-FOR:$ip", "CLIENT-IP:$ip");
        ob_start();
        $ch = curl_init();
        curl_setopt ($ch, CURLOPT_URL, $url);
        curl_setopt ($ch, CURLOPT_HTTPHEADER , $headers );
        curl_setopt ($ch, CURLOPT_REFERER, "pay.o9di.cn");
        curl_setopt( $ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Linux; Android 6.0.1; OPPO R11s Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36');
        curl_exec($ch);
        curl_close ($ch);
        $out = ob_get_contents();
        ob_clean();
        $a = preg_match('/weixin:\/\/wap.*/',$out, $str);
        if ($a) {
            return substr($str[0], 0, strlen($str[0])-1);
        } else {
            return '';
        }
    }
    public static function httpPost($url='',$post_data=array(),$header=array(),$timeout=30) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳過證書檢查
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 從證書中檢查SSL加密算法是否存在
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);

        $response = curl_exec($ch);

        curl_close($ch);

        return $response;
    }
    public static function getClientIp($type = 0) {
        $type       =  $type ? 1 : 0;
        $ip         =   'unknown';
        if ($ip !== 'unknown') return $ip[$type];
        if($_SERVER['HTTP_X_REAL_IP']){//nginx 代理模式下宛畦,獲取客戶端真實(shí) IP
            $ip=$_SERVER['HTTP_X_REAL_IP'];
        }elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {//客戶端的 ip
            $ip     =   $_SERVER['HTTP_CLIENT_IP'];
        }elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {//瀏覽當(dāng)前頁面的用戶計(jì)算機(jī)的網(wǎng)關(guān)
            $arr    =   explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $pos    =   array_search('unknown',$arr);
            if(false !== $pos) unset($arr[$pos]);
            $ip     =   trim($arr[0]);
        }elseif (isset($_SERVER['REMOTE_ADDR'])) {
            $ip     =   $_SERVER['REMOTE_ADDR'];//瀏覽當(dāng)前頁面的用戶計(jì)算機(jī)的 ip 地址
        }else{
            $ip=$_SERVER['REMOTE_ADDR'];
        }
        // IP 地址合法驗(yàn)證
        $long = sprintf("%u",ip2long($ip));
        $ip   = $long ? array($ip, $long) : array('0.0.0.0', 0);
        return $ip[$type];
    }
}

請求參數(shù)

請求微信統(tǒng)一下單接口

接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder

  • appid 微信公眾號iD
  • mch_id賬戶號
  • nonce_str 隨機(jī)字符串瘸洛,不長于32位
  • sign 簽名
  • body 商品描述
  • out_trade_no 商戶訂單號,不長于32位
  • total_fee 總金額次和,以分為單位
  • spbill_create_ip 用戶端請求支付時的IP
  • notify_url 異步通知回調(diào)地址反肋,必須是可直接訪問地址,不能攜帶參數(shù)
  • trade_type 交易類型踏施,如H5則是MWEB
<xml>
<appid>$appid</appid>
<body>$body</body>
<mch_id>$mch_id</mch_id>
<nonce_str>$nonce_str</nonce_str>
<notify_url>$notify_url</notify_url>
<out_trade_no>$out_trade_no</out_trade_no>
<scene_info>$scene_info</scene_info>
<spbill_create_ip>$spbill_create_ip</spbill_create_ip>
<total_fee>$total_fee</total_fee>
<trade_type>$trade_type</trade_type>
<sign>$sign</sign>
</xml>

簽名生成

  1. 參與生成簽名的參數(shù)必須非空
  2. 參數(shù)按照ASCII碼由小到大排序石蔗,參數(shù)名區(qū)分大小寫
  3. 按照上述規(guī)則罕邀,將參數(shù)拼接成如k1=v1&k2=v2....的字符串
  4. 將上一步得到的字符串拼接上key, 如k1=v1&k2=v2&key=192006250b4c09247ec02e
  5. 再將最后得到的字符串進(jìn)行MD5加密养距,再轉(zhuǎn)為大寫燃少,即為最終的sign值。
$signA ="appid=$appid&body=$body&mch_id=$mch_id&nonce_str=$nonce_str&notify_url=$notify_url&out_trade_no=$out_trade_no&scene_info=$scene_info&spbill_create_ip=$spbill_create_ip&total_fee=$total_fee&trade_type=$trade_type";

//拼接字符串 注意順序微信有個測試網(wǎng)址 順序按照他的來 直接點(diǎn)下面的校正測試 包括下面XML 是否正確
$strSignTmp = $signA."&key=$key";
       
// MD5 后轉(zhuǎn)換成大寫
$sign = strtoupper(MD5($strSignTmp));

封裝函數(shù)

/**
 * 生成簽名
 *  @return 簽名
 */
function makeSign( $params ){
    //簽名步驟一:按字典序排序數(shù)組參數(shù)
    ksort($params);
    $string = makeUrlParams($params);
    //簽名步驟二:在string后加入KEY
    $string = $string . "&key=".$this->key;
    //簽名步驟三:MD5加密
    $string = md5($string);
    //簽名步驟四:所有字符轉(zhuǎn)為大寫
    $result = strtoupper($string);
    return $result;
}
/**
 * 將參數(shù)拼接為url: key=value&key=value
 * @param   $params
 * @return  string
 */
function makeUrlParams( $params ){
    $string = '';
    if( !empty($params) ){
        $array = array();
        foreach( $params as $key => $value ){
            $array[] = $key.'='.$value;
        }
        $string = implode("&",$array);
    }
    return $string;
}

用戶IP

H5支付要求商戶在統(tǒng)一下單接口中上傳用戶真實(shí)IP地址spbill_create_ip铃在,為保證微信端獲取的用戶IP地址與商戶端獲取的一致,提供了以下獲取用戶IP的指引碍遍。

  • 沒有代理的情況

在商戶的前端接入層沒有做代理的情況下獲取IP的方式比較簡單定铜,直接獲取REMOTE_ADDR即可。

  • 有代理的情況

在有代理的情況下怕敬,因?yàn)橐婵蛻舳巳ピL問服務(wù)器揣炕,所以,當(dāng)請求包經(jīng)過反向代理后东跪,在代理服務(wù)器這里這個IP數(shù)據(jù)包的IP包頭做了修改畸陡,最終后端WEB服務(wù)器得到的數(shù)據(jù)包的頭部源IP地址是代理服務(wù)器的IP地址。這樣一來虽填,后端服務(wù)器的程序就無法獲取用戶的真實(shí)IP丁恭。

在Nginx中配置中加入

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Real-Port $remote_port;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

函數(shù)封裝

function getClientIp($type = 0) {
    $type       =  $type ? 1 : 0;
    $ip         =   'unknown';
    if ($ip !== 'unknown') return $ip[$type];
    if(isset($_SERVER["HTTP_X_REAL_IP"]) && !empty($_SERVER['HTTP_X_REAL_IP'])){//nginx 代理模式下,獲取客戶端真實(shí) IP
        $ip=$_SERVER['HTTP_X_REAL_IP'];
    }elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {//客戶端的 ip
        $ip     =   $_SERVER['HTTP_CLIENT_IP'];
    }elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {//瀏覽當(dāng)前頁面的用戶計(jì)算機(jī)的網(wǎng)關(guān)
        $arr    =   explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $pos    =   array_search('unknown',$arr);
        if(false !== $pos) unset($arr[$pos]);
        $ip     =   trim($arr[0]);
    }elseif (isset($_SERVER['REMOTE_ADDR'])) {
        $ip     =   $_SERVER['REMOTE_ADDR'];//瀏覽當(dāng)前頁面的用戶計(jì)算機(jī)的 ip 地址
    }else{
        $ip=$_SERVER['REMOTE_ADDR'];
    }
    // IP 地址合法驗(yàn)證
    $long = sprintf("%u",ip2long($ip));
    $ip   = $long ? array($ip, $long) : array('0.0.0.0', 0);
    return $ip[$type];
}

接口返回

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxdded766660f9b840]]></appid>
<mch_id><![CDATA[1516216351]]></mch_id>
<device_info><![CDATA[100]]></device_info>
<nonce_str><![CDATA[2DUN2i2pGnlC6vDi]]></nonce_str>
<sign><![CDATA[95CEA831D598299097A32D8FEEC6BDEF]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx22194530678545eb3713f2f10724143329]]></prepay_id>
<trade_type><![CDATA[MWEB]]></trade_type>
<mweb_url><![CDATA[https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx22194530678545eb3713f2f10724143329&package=87106983]]></mweb_url>
  • return_codeSUCCESS代表支付請求成功
/**
 * 錯誤代碼
 * @param  $code       服務(wù)器輸出的錯誤代碼
 * return string
 */
function getErrorMessage( $code ){
    $errList = array(
        'NOAUTH'                =>  '商戶未開通此接口權(quán)限',
        'NOTENOUGH'             =>  '用戶帳號余額不足',
        'ORDERNOTEXIST'         =>  '訂單號不存在',
        'ORDERPAID'             =>  '商戶訂單已支付斋日,無需重復(fù)操作',
        'ORDERCLOSED'           =>  '當(dāng)前訂單已關(guān)閉牲览,無法支付',
        'SYSTEMERROR'           =>  '系統(tǒng)錯誤!系統(tǒng)超時',
        'APPID_NOT_EXIST'       =>  '參數(shù)中缺少APPID',
        'MCHID_NOT_EXIST'       =>  '參數(shù)中缺少M(fèi)CHID',
        'APPID_MCHID_NOT_MATCH' =>  'appid和mch_id不匹配',
        'LACK_PARAMS'           =>  '缺少必要的請求參數(shù)',
        'OUT_TRADE_NO_USED'     =>  '同一筆交易不能多次提交',
        'SIGNERROR'             =>  '參數(shù)簽名結(jié)果不正確',
        'XML_FORMAT_ERROR'      =>  'XML格式錯誤',
        'REQUIRE_POST_METHOD'   =>  '未使用post傳遞參數(shù) ',
        'POST_DATA_EMPTY'       =>  'post數(shù)據(jù)不能為空',
        'NOT_UTF8'              =>  '未使用指定編碼格式',
    );
    if( array_key_exists( $code , $errList ) ){
        return $errList[$code];
    }
}
  • mweb_url為支付跳轉(zhuǎn)頁,此時客戶端通過mweb_url已經(jīng)可以調(diào)起微信支付

mweb_url是為拉起微信支付收銀臺的中間頁面恶守,可通過訪問該URL來拉起微信客戶端第献,完成支付,mweb_url的有效期為 5 分鐘兔港。

https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096

正常流程用戶支付完成后會返回至發(fā)起支付的頁面庸毫,如需返回至指定頁面,則可以在mweb_url后拼接上redirect_url參數(shù)(需對redirect_url進(jìn)行urlencode處理)衫樊,來指定回調(diào)頁面飒赃。

在設(shè)置redirect_url時需要注意的是設(shè)置的回跳地址的域名與申請H5支付時提交的授權(quán)域名是否一致,否則會出現(xiàn)錯誤科侈,若想通過redirect_url跳轉(zhuǎn)到本域名之外的地址盒揉,可以先跳回域名內(nèi)地址之后再跳出到域名外的地址。

https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096&redirect_url=https%3A%2F%2Fwww.wechatpay.com.cn

微信的移動端WAP支付兑徘,在非微信瀏覽器中刚盈,需要通過微信的H5支付方式來喚起微信支付客戶端,實(shí)現(xiàn)是通過拼裝一個DeepLink鏈接挂脑,并訪問該鏈接達(dá)到喚起微信支付客戶端藕漱,客戶端也可以通過此DeepLink值直接調(diào)起支付欲侮。通過測試發(fā)現(xiàn),存在瀏覽器兼容問題肋联。

weixin://wap/pay?prepayid%3Dwx22201221074146ac747121890095299503&package=2656135616&noncestr=1542888966&sign=e31dbc2d1231708ff8a982b15a6c7646

在得到微信返回的mweb_url參數(shù)后威蕉,可在服務(wù)端進(jìn)一步獲得DeepLink

/**
 * 獲取微信支付中間頁deepLink參數(shù)
 * @param string $url 微信返回的mweb_url
 * @param string $ip 用戶端IP
 */
function getDeeplink(string $url, string $ip)
{
    $headers = array("X-FORWARDED-FOR:$ip", "CLIENT-IP:$ip");
    ob_start();
    $ch = curl_init();
    curl_setopt ($ch, CURLOPT_URL, $url);
    curl_setopt ($ch, CURLOPT_HTTPHEADER , $headers );
    curl_setopt ($ch, CURLOPT_REFERER, "pay.o9di.cn");
    curl_setopt( $ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Linux; Android 6.0.1; OPPO R11s Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36');
    curl_exec($ch);
    curl_close ($ch);
    $out = ob_get_contents();
    ob_clean();
    $a = preg_match('/weixin:\/\/wap.*/',$out, $str);
    if ($a) {
        return substr($str[0], 0, strlen($str[0])-1);
    } else {
        return '';
    }
}

支付中間頁

支付中間頁
支付中間頁

同步回調(diào)

正常流程用戶支付完成后會返回至發(fā)起支付的頁面橄仍,如需返回至指定頁面韧涨,則可以在 MWEB_URL 后拼接上 redirect_url 參數(shù),來指定回調(diào)頁面侮繁。需對 redirect_url 進(jìn)行 urlencode 處理虑粥。

$redirect_url  = "https://www.wechatpay.com.cn";
$redirect_url = urlencode($redirect_url);
// 返回至指定頁面
$mweb_url = $mweb_url."&redirect_url=".$redirect_url;

由于設(shè)置redirect_url后,回跳指定頁面的操作可能發(fā)生在:

  1. 微信支付中間頁調(diào)起微信收銀臺后超過5秒
  2. 用戶點(diǎn)擊“取消支付“或支付完成后點(diǎn)“完成”按鈕。

因此無法保證頁面回跳時宪哩,支付流程已結(jié)束娩贷,所以商戶設(shè)置的redirect_url地址不能自動執(zhí)行查單操作,應(yīng)讓用戶去點(diǎn)擊按鈕觸發(fā)查單操作锁孟。

異步回調(diào)

// 接收text/xml數(shù)據(jù)
$str_Post = file_get_contents("php://input");

// 禁止引用外部XML實(shí)體
libxml_disable_entity_loader(true);

$postObj = simplexml_load_string($str_Post, 'SimpleXMLElement', LIBXML_NOCDATA);

$postObj = json_encode($postObj);
$postObj = json_decode($postObj, true);

$result_code = trim($postObj["result_code"]);
$return_code = trim($postObj["return_code"]);
$sign = trim($postObj["sign"]);
$out_trade_no = trim($postObj["out_trade_no"]);

if ($result_code  == 'SUCCESS' && $return_code == 'SUCCESS') {

}

類庫

<?php
/**
 * 微信支付服務(wù)器端下單
 * 微信APP支付文檔地址:  https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=8_6
 * 使用示例
 *  構(gòu)造方法參數(shù)
 *      'appid'     =>  //填寫微信分配的公眾賬號ID
 *      'mch_id'    =>  //填寫微信支付分配的商戶號
 *      'notify_url'=>  //填寫微信支付結(jié)果回調(diào)地址
 *      'key'       =>  //填寫微信商戶支付密鑰
 *  );
 *  統(tǒng)一下單方法
 *  $obj = new WxPay($options);
 *  $params['body'] = '商品描述';                   //商品描述
 *  $params['out_trade_no'] = '1217752501201407';   //自定義的訂單號彬祖,不能重復(fù)
 *  $params['total_fee'] = '100';                   //訂單金額 只能為整數(shù) 單位為分
 *  $params['trade_type'] = 'APP';                  //交易類型 JSAPI | NATIVE |APP | WAP
 *  $obj->unifiedOrder( $params );
 */
class WxPay
{
    //接口API URL前綴
    const API_URL_PREFIX = 'https://api.mch.weixin.qq.com';
    //下單地址URL
    const UNIFIEDORDER_URL = "/pay/unifiedorder";
    //查詢訂單URL
    const ORDERQUERY_URL = "/pay/orderquery";
    //關(guān)閉訂單URL
    const CLOSEORDER_URL = "/pay/closeorder";
    //公眾賬號ID
    private $appid;
    //商戶號
    private $mch_id;
    //隨機(jī)字符串
    private $nonce_str;
    //簽名
    private $sign;
    //商品描述
    private $body;
    //商戶訂單號
    private $out_trade_no;
    //支付總金額
    private $total_fee;
    //終端IP
    private $spbill_create_ip;
    //支付結(jié)果回調(diào)通知地址
    private $notify_url;
    //交易類型
    private $trade_type;
    //支付密鑰
    private $key;
    //證書路徑
    private $SSLCERT_PATH;
    private $SSLKEY_PATH;
    //所有參數(shù)
    private $params = [];
    
    public function __construct($appid, $mch_id, $notify_url, $key)
    {
        $this->appid = $appid;
        $this->mch_id = $mch_id;
        $this->notify_url = $notify_url;
        $this->key = $key;
    }
    /** 場景信息 必要參數(shù)*/
    public function buildSceneInfo($wap_url, $wap_name="支付")
    {
        return '{"h5_info":{"type":"Wap","wap_url":"'.$wap_url.'","wap_name":"'.$wap_name.'"}}';
    }
    /**
     * 下單方法
     * @param   $params 下單參數(shù)
     */
    public function unifiedOrder( $params ){
        $this->body = $params['body'];
        $this->out_trade_no = $params['out_trade_no'];
        $this->total_fee = $params['total_fee'];
        $this->trade_type = $params['trade_type'];
        $this->scene_info = $params['scene_info'];
        $this->nonce_str = $this->genRandomString();
        $this->spbill_create_ip = $_SERVER['REMOTE_ADDR'];
        
        $this->params['appid'] = $this->appid;
        $this->params['mch_id'] = $this->mch_id;
        $this->params['nonce_str'] = $this->nonce_str;
        $this->params['body'] = $this->body;
        $this->params['out_trade_no'] = $this->out_trade_no;
        $this->params['total_fee'] = $this->total_fee;
        $this->params['spbill_create_ip'] = $this->spbill_create_ip;
        $this->params['notify_url'] = $this->notify_url;
        $this->params['trade_type'] = $this->trade_type;
        $this->params['scene_info'] = $this->scene_info;
        
        //獲取簽名數(shù)據(jù)
        $this->sign = $this->makeSign( $this->params );
        $this->params['sign'] = $this->sign;
        
        $xml = $this->makeArrayToXml($this->params);
        $response = $this->postXmlCurl($xml, self::API_URL_PREFIX.self::UNIFIEDORDER_URL);
        if( !$response ){
            return false;
        }
        
        $result = $this->parseXmlToArray( $response );
        if( !empty($result['result_code']) && !empty($result['err_code']) ){
            $result['err_msg'] = $this->getErrorMessage( $result['err_code'] );
        }
        return $result;
    }

    /**
     * 獲取微信支付中間頁deepLink參數(shù)
     * 獲取deepLink客戶端通過次鏈接可直接調(diào)起支付
     * @param string $url 微信返回的mweb_url
     * @param string $ip 用戶端IP
     */
    public function getDeepLink($url, $ip)
    {
        $headers = ["X-FORWARDED-FOR:$ip", "CLIENT-IP:$ip"];

        ob_start();
        $ch = curl_init();
        curl_setopt ($ch, CURLOPT_URL, $url);
        curl_setopt ($ch, CURLOPT_HTTPHEADER , $headers );
        curl_setopt ($ch, CURLOPT_REFERER, "pay.o9di.cn");
        curl_setopt( $ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Linux; Android 6.0.1; OPPO R11s Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36');
        curl_exec($ch);
        curl_close ($ch);
        $out = ob_get_contents();
        ob_clean();

        $a = preg_match('/weixin:\/\/wap.*/',$out, $str);
        if ($a) {
            return substr($str[0], 0, strlen($str[0])-1);
        } else {
            return '';
        }
    }

    /**
     * 查詢訂單信息
     * @param $out_trade_no     訂單號
     * @return array
     */
    public function orderQuery( $out_trade_no ){
        $this->params['appid'] = $this->appid;
        $this->params['mch_id'] = $this->mch_id;
        $this->params['nonce_str'] = $this->genRandomString();
        $this->params['out_trade_no'] = $out_trade_no;

        //獲取簽名數(shù)據(jù)
        $this->sign = $this->makeSign( $this->params );
        $this->params['sign'] = $this->sign;
        $xml = $this->makeArrayToXml($this->params);
        $response = $this->postXmlCurl($xml, self::API_URL_PREFIX.self::ORDERQUERY_URL);
        if( !$response ){
            return false;
        }
        $result = $this->parseXmlToArray( $response );
        if( !empty($result['result_code']) && !empty($result['err_code']) ){
            $result['err_msg'] = $this->getErrorMessage( $result['err_code'] );
        }
        return $result;
    }
    /**
     * 關(guān)閉訂單
     * @param $out_trade_no     訂單號
     * @return array
     */
    public function closeOrder( $out_trade_no ){
        $this->params['appid'] = $this->appid;
        $this->params['mch_id'] = $this->mch_id;
        $this->params['nonce_str'] = $this->genRandomString();
        $this->params['out_trade_no'] = $out_trade_no;
        //獲取簽名數(shù)據(jù)
        $this->sign = $this->makeSign( $this->params );
        $this->params['sign'] = $this->sign;
        $xml = $this->makeArrayToXml($this->params);
        $response = $this->postXmlCurl($xml, self::API_URL_PREFIX.self::CLOSEORDER_URL);
        if( !$response ){
            return false;
        }
        $result = $this->parseXmlToArray( $response );
        return $result;
    }
    /**
     *
     * 獲取支付結(jié)果通知數(shù)據(jù)
     * return array
     */
    public function getNotifyData(){
        //獲取通知的數(shù)據(jù)
        $xml = $GLOBALS['HTTP_RAW_POST_DATA'];
        $data = array();
        if( empty($xml) ){
            return false;
        }
        $data = $this->parseXmlToArray( $xml );
        if( !empty($data['return_code']) ){
            if( $data['return_code'] == 'FAIL' ){
                return false;
            }
        }
        return $data;
    }
    /**
     * 接收通知成功后應(yīng)答輸出XML數(shù)據(jù)
     * @param string $xml
     */
    public function replyNotify(){
        $data['return_code'] = 'SUCCESS';
        $data['return_msg'] = 'OK';
        $xml = $this->makeArrayToXml( $data );
        echo $xml;
        die();
    }
    /**
     * 生成APP端支付參數(shù)
     * @param  $prepayid   預(yù)支付id
     */
    public function getAppPayParams( $prepayid ){
        $data = [];
        $data['appid'] = $this->appid;
        $data['partnerid'] = $this->mch_id;
        $data['prepayid'] = $prepayid;
        $data['package'] = 'Sign=WXPay';
        $data['noncestr'] = $this->genRandomString();
        $data['timestamp'] = time();
        $data['sign'] = $this->makeSign( $data );
        return $data;
    }
    /**
     * 生成簽名
     *  @return 簽名
     */
    public function makeSign( $params ){
        //簽名步驟一:按字典序排序數(shù)組參數(shù)
        ksort($params);
        $string = $this->ToUrlParams($params);
        //簽名步驟二:在string后加入KEY
        $string = $string . "&key=".$this->key;
        //簽名步驟三:MD5加密
        $string = md5($string);
        //簽名步驟四:所有字符轉(zhuǎn)為大寫
        $result = strtoupper($string);
        return $result;
    }
    /**
     * 將參數(shù)拼接為url: key=value&key=value
     * @param   $params
     * @return  string
     */
    public function ToUrlParams( $params ){
        $string = '';
        if( !empty($params) ){
            $array = array();
            foreach( $params as $key => $value ){
                $array[] = $key.'='.$value;
            }
            $string = implode("&",$array);
        }
        return $string;
    }
    /**
     * 輸出xml字符
     * @param   $params     參數(shù)名稱
     * return   string      返回組裝的xml
     **/
    public function makeArrayToXml( $params ){
        if(!is_array($params)|| count($params) <= 0)
        {
            return false;
        }
        $xml = "<xml>";
        foreach ($params as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    /**
     * 將xml轉(zhuǎn)為array
     * @param string $xml
     * return array
     */
    public function parseXmlToArray($xml){
        if(!$xml){
            return false;
        }
        //將XML轉(zhuǎn)為array
        //禁止引用外部xml實(shí)體
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
    }
    /**
     * 獲取毫秒級別的時間戳
     */
    private static function getMillisecond(){
        //獲取毫秒的時間戳
        $time = explode ( " ", microtime () );
        $time = $time[1] . ($time[0] * 1000);
        $time2 = explode( ".", $time );
        $time = $time2[0];
        return $time;
    }
    /**
     * 產(chǎn)生一個指定長度的隨機(jī)字符串,并返回給用戶
     * @param type $len 產(chǎn)生字符串的長度
     * @return string 隨機(jī)字符串
     */
    private function genRandomString($len = 32) {
        $chars = array(
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
            "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
            "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
            "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
            "3", "4", "5", "6", "7", "8", "9"
        );
        $charsLen = count($chars) - 1;
        // 將數(shù)組打亂 
        shuffle($chars);
        $output = "";
        for ($i = 0; $i < $len; $i++) {
            $output .= $chars[mt_rand(0, $charsLen)];
        }
        return $output;
    }
    /**
     * 以post方式提交xml到對應(yīng)的接口url
     *
     * @param string $xml  需要post的xml數(shù)據(jù)
     * @param string $url  url
     * @param bool $useCert 是否需要證書,默認(rèn)不需要
     * @param int $second   url執(zhí)行超時時間品抽,默認(rèn)30s
     * @throws WxPayException
     */
    private function postXmlCurl($xml, $url, $useCert = false, $second = 30){
        $ch = curl_init();
        //設(shè)置超時
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);
        //設(shè)置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求結(jié)果為字符串且輸出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        if($useCert == true){
            //設(shè)置證書
            //使用證書:cert 與 key 分別屬于兩個.pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            //curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            //curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //運(yùn)行curl
        $data = curl_exec($ch);
        //返回結(jié)果
        if($data){
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            return false;
        }
    }
    /**
     * 錯誤代碼
     * @param  $code       服務(wù)器輸出的錯誤代碼
     * return string
     */
    public function getErrorMessage( $code ){
        $errList = array(
            'NOAUTH'                =>  '商戶未開通此接口權(quán)限',
            'NOTENOUGH'             =>  '用戶帳號余額不足',
            'ORDERNOTEXIST'         =>  '訂單號不存在',
            'ORDERPAID'             =>  '商戶訂單已支付储笑,無需重復(fù)操作',
            'ORDERCLOSED'           =>  '當(dāng)前訂單已關(guān)閉,無法支付',
            'SYSTEMERROR'           =>  '系統(tǒng)錯誤!系統(tǒng)超時',
            'APPID_NOT_EXIST'       =>  '參數(shù)中缺少APPID',
            'MCHID_NOT_EXIST'       =>  '參數(shù)中缺少M(fèi)CHID',
            'APPID_MCHID_NOT_MATCH' =>  'appid和mch_id不匹配',
            'LACK_PARAMS'           =>  '缺少必要的請求參數(shù)',
            'OUT_TRADE_NO_USED'     =>  '同一筆交易不能多次提交',
            'SIGNERROR'             =>  '參數(shù)簽名結(jié)果不正確',
            'XML_FORMAT_ERROR'      =>  'XML格式錯誤',
            'REQUIRE_POST_METHOD'   =>  '未使用post傳遞參數(shù) ',
            'POST_DATA_EMPTY'       =>  'post數(shù)據(jù)不能為空',
            'NOT_UTF8'              =>  '未使用指定編碼格式',
        );
        if( array_key_exists( $code , $errList ) ){
            return $errList[$code];
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末圆恤,一起剝皮案震驚了整個濱河市南蓬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哑了,老刑警劉巖赘方,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異弱左,居然都是意外死亡窄陡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門拆火,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跳夭,“玉大人,你說我怎么就攤上這事们镜”姨荆” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵模狭,是天一觀的道長颈抚。 經(jīng)常有香客問我,道長嚼鹉,這世上最難降的妖魔是什么贩汉? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任驱富,我火速辦了婚禮,結(jié)果婚禮上匹舞,老公的妹妹穿的比我還像新娘褐鸥。我一直安慰自己,他們只是感情好赐稽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布叫榕。 她就那樣靜靜地躺著,像睡著了一般姊舵。 火紅的嫁衣襯著肌膚如雪晰绎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天蠢莺,我揣著相機(jī)與錄音,去河邊找鬼零如。 笑死躏将,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的考蕾。 我是一名探鬼主播祸憋,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肖卧!你這毒婦竟也來了蚯窥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤塞帐,失蹤者是張志新(化名)和其女友劉穎拦赠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體葵姥,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荷鼠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了榔幸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片允乐。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖削咆,靈堂內(nèi)的尸體忽然破棺而出牍疏,到底是詐尸還是另有隱情,我是刑警寧澤拨齐,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布鳞陨,位于F島的核電站,受9級特大地震影響瞻惋,放射性物質(zhì)發(fā)生泄漏炊邦。R本人自食惡果不足惜编矾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馁害。 院中可真熱鬧窄俏,春花似錦、人聲如沸碘菜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忍啸。三九已至仰坦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間计雌,已是汗流浹背悄晃。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凿滤,地道東北人妈橄。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像翁脆,于是被迫代替她去往敵國和親眷蚓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容