微信小程序支付

<?php
class Index{
    // 首先獲取小程序以下配置
    private $wxpayconf = [
            'appid'           => 'wx0ad699db1a9ff6d3', // 小程序appid
            'mch_id'          => 1513841901, // 小程序商戶號
            'key'             => 'qwer122asdauiyfue1nm65qa12dds1r1', //  key 獲取設置路徑:微信商戶平臺(pay.weixin.qq.com)-->帳戶設置-->API安全-->密鑰設置,支付API祕鑰
            //'appsecret'       => 'd624aca96d0349eec924cde1baec36cb', // 小程序 APPSecret,這裏暫時用不到
                
            'order_url'       => 'https://api.mch.weixin.qq.com/pay/unifiedorder', //統一下單接口地址
            'orderquery_url'  => 'https://api.mch.weixin.qq.com/pay/orderquery',//查詢訂單接口地址
            'closeorder_url'  => 'https://api.mch.weixin.qq.com/pay/closeorder',//關閉訂單接口地址
            'refund_url'      => 'https://api.mch.weixin.qq.com/secapi/pay/refund',//申請退款接口地址
            'refundquery_url' => 'https://api.mch.weixin.qq.com/pay/refundquery'//查詢退款接口地址
                
    ];
    
    
    
    /*
     * 1.預支付 經過微信 https://api.mch.weixin.qq.com/pay/unifiedorder 接口進行預支付,詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1
     * 2.經過預支付返回的prepay_id 進行從新組建數據,組建成功後返回給前端,前端進行彈框支付 ,從新組建數據詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3
     * 3.notify_url 地址就是回調地址,在前端支付成功後,微信會往這個地址發送支付成功訂單的相關信息,小程序這個地址不容許有參數,在這個地址中處理微信發送的支付成功訂單信息
     */
    // 統一下單
    function index() {
        //這裏是封裝支付接口給服務商,因此一些商品信息須要獲取
        $openid       = $_POST ['openid'];
        $notify_url   = $_POST ['notify_url']; // 支付成功後回調地址,相似 http://www.gaoqingsong.com/index.php 這樣後面不能有參數,這裏由於封裝接口給別人用,因此對方須要提供一個回調接口,將微信回調的xml信息返回給對方
        $body         = $_POST ['body']; //商品描述
        $out_trade_no = $_POST ['out_trade_no']; // 訂單號
        $total_fee    = $_POST ['total_fee']; // 訂單總金額,單位爲分
        $userIP       = $_POST ['userIP'];
        
        // 遠程終端ip
        $userIP    = !empty($userIP) ? $userIP : $_SERVER ['REMOTE_ADDR'];
        //獲取隨機字符串
        $nonce_str = $this->getNonceStr ();
        
        $sign ['appid']            = $this->wxpayconf ['appid'];
        $sign ['mch_id']           = $this->wxpayconf ['mch_id'];
        $sign ['nonce_str']        = $nonce_str;
        $sign ['body']             = !empty($body) ? $body : '集妝箱商品';
        $sign ['out_trade_no']     = $out_trade_no;
        $sign ['total_fee']        = $total_fee;
        $sign ['spbill_create_ip'] = $userIP; // 終端客戶端ip
        $sign ['trade_type']       = 'JSAPI'; // 小程序支付值固定:JSAPI
        $sign ['openid']           = $openid;
        $sign ['notify_url']       = 'https://xcxoauth.beauty-box.cn/index/index/payres';//$notify_url;//先將客戶的$notify_url保存在訂單表數據中(代碼中未寫),微信回調先調用內部的payres方法,payres記錄相關信息後,再找到對應訂單數據中的$notify_url將微信的xml信息回調給客戶;
        $sign ['sign']             = $this->getSign ( $sign, $this->wxpayconf ['key'] );
        // 微信統一下單接口
        // 先將數組換成XML格式
        $xmlData = $this->arrayToXml ( $sign );
        // 提交訂單數據,預支付
        $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['order_url'] );
        // 將返回信息從XML還原回數組
        $wx_result = $this->xmlToArray ( $xml_result );

        //若是預支付提交訂單沒問題,則組織相關信息返回給前端,進行支付方,支付成功後,微信會往回調頁 $notify_url 發送訂單信息
        if ($wx_result ['return_code'] == 'SUCCESS' && $wx_result ['result_code'] == 'SUCCESS') {
            $prepay_id = $wx_result ['prepay_id'];
            
            $return ['appId']     = $this->wxpayconf ['appid'];
            $return ['timeStamp'] = ( string ) time ();
            $return ['nonceStr']  = $nonce_str;
            $return ['package']   = 'prepay_id=' . $prepay_id;
            $return ['signType']  = 'MD5';
            $return ['paySign']   = $this->getSign ( $return, $this->wxpayconf ['key'] );
            echo json_encode ( $return );
        } else {
            if ($wx_result ['return_code'] == 'FAIL') {
                $info ['return_msg'] = $wx_result ['return_msg'];
            }
            if ($wx_result ['return_code'] == 'SUCCESS' && $wx_result ['result_code'] == 'FAIL') {
                $info ['err_code']     = $wx_result ['err_code'];
                $info ['err_code_des'] = $wx_result ['err_code_des'];
            }
            echo json_encode ( $info );
        }
    }
    
    
    
    
    //支付結果回調方法
    public function payres(){
        //記錄日誌
        $log_filename = "log/payres.txt";
        $log_content = ' ================    訪問接口 payres  ================= 時間 : ' . date("Y-m-d H:i:s");
        file_put_contents ( $log_filename, $log_content, FILE_APPEND );
    
        //獲取微信回調post傳回的xml數據
        $xml_result = file_get_contents('php://input');
    
        $log_content = "\n\r\n\r=====接收到的xml數據 : " . $xml_result . " ======================================================\n\r";
        file_put_contents ( $log_filename, $log_content, FILE_APPEND );
    
    
        // 將返回信息從XML還原回數組
        $wx_result = $this->xmlToArray ( $xml_result );
        
        //訂單表數據對象,這裏假設本地有訂單表,字段名和微信訂單參數名一致,保存下單時的訂單數據
        //這裏模型框架用 thinkPHP5
        $this->order_model = new OrderModel ();
    
        if ($wx_result ['return_code'] == 'SUCCESS') {
            //先獲取下訂單數據
            $where = array();
            $where['out_trade_no'] = $wx_result['out_trade_no'];
            $where['nonce_str']    = $wx_result['nonce_str'];
            //$where['sign']         = $wx_result['sign'];
            $where['total_fee']    = $wx_result['total_fee'];
            $order = $this->order_model->where($where)->find();
                
            //訂單信息
            $log_content = "\n\r\n\r=====查詢本地的對應訂單信息 : " . json_encode($order) . " ======================================================\n\r";
            file_put_contents ( $log_filename, $log_content, FILE_APPEND );
                
            //若是訂單存在而且爲未支付狀態,訂單表字段 pay_status 支付狀態,1成功,默認0待支付
            if(!empty($order['id']) && $order['pay_status'] != 1){
    
                //支付成功後保存數據,並往供應商提供的回調頁面傳微信回調信息
                $data = array();
                $data['id']             = $order['id'];
                $data['time_end']       = !empty($wx_result['time_end'])?$wx_result['time_end']:null;
                $data['openid']         = !empty($wx_result['openid'])?$wx_result['openid']:null;
                $data['bank_type']      = !empty($wx_result['bank_type'])?$wx_result['bank_type']:null;
                $data['cash_fee']       = !empty($wx_result['cash_fee'])?$wx_result['cash_fee']:null;
                $data['transaction_id'] = !empty($wx_result['transaction_id'])?$wx_result['transaction_id']:null;
                $data['return_code']    = !empty($wx_result['return_code'])?$wx_result['return_code']:null;
                $data['result_code']    = !empty($wx_result['result_code'])?$wx_result['result_code']:null;
                $data['notify_str']     = $xml_result;
                $data['notify_time']    = date("Y-m-d H:i:s");
    
                if($wx_result ['result_code'] == 'SUCCESS'){
                    $data['pay_status'] = 1;
                }
    
                //支付成功後保存的數據
                $log_content = "\n\r\n\r=====支付成功後保存的數據 : " . json_encode($data) . " ======================================================\n\r";
                file_put_contents ( $log_filename, $log_content, FILE_APPEND );
    
    
                //保存支付數據
                if($this->order_model->save($data, $data['id']) !== false){
                    /*
                    //若是是封裝支付接口,則還需把微信返回的xml數據傳給接口使用者
                    //給第三方返回信息
                    $this->postXmlCurl ( $xml_result, $order ['notify_url'] );
                        
                    //保存成功後回傳客戶端的url
                    $log_content = "\n\r\n\r=====保存成功後回傳客戶端的url : " . $order ['notify_url'] . " ======================================================\n\r";
                    $log_content .= "\n\r\n\r=====保存成功後回傳客戶端的xml : " . $xml_result . " ======================================================\n\r";
                    file_put_contents ( $log_filename, $log_content, FILE_APPEND );
                    */
                    //給微信返回信息
                    $sign ['return_code'] = 'SUCCESS';
                    $sign ['return_msg']  = 'OK';
                    // 先將數組換成XML格式
                    $xmlData = $this->arrayToXml ( $sign );
                        
                    $log_content = "\n\r\n\r=====返回給微信端 xml : " . $xmlData . " ======================================================\n\r";
                    $log_content .= "\n\r\n\r===== payres 接口結束======================================================\n\r";
                    file_put_contents ( $log_filename, $log_content, FILE_APPEND );
                    echo $xmlData;exit;
                }
            }
        }
    
        $log_content = "\n\r\n\r===== payres 接口結束======================================================\n\r";
        file_put_contents ( $log_filename, $log_content, FILE_APPEND );
    }

    
    
    
    //查詢訂單,詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2
    public function orderquery(){
        $out_trade_no = $_POST ['out_trade_no']; // 商戶訂單號
    
        //獲取隨機字符串
        $nonce_str = $this->getNonceStr ();
    
        $sign ['appid']            = $this->wxpayconf ['appid'];
        $sign ['mch_id']           = $this->wxpayconf ['mch_id'];
        $sign ['out_trade_no']     = $out_trade_no;
        $sign ['nonce_str']        = $nonce_str;
        $sign ['sign']             = $this->getSign ( $sign, $this->wxpayconf ['key'] );
    
        // 先將數組換成XML格式
        $xmlData = $this->arrayToXml ( $sign );
        // 提交查詢訂單數據
        $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['orderquery_url'] );
        // 將返回信息從XML還原回數組
        $wx_result = $this->xmlToArray ( $xml_result );
        //從查詢訂單起,返回結果不須要再簽名,直接將結果返回,判斷邏輯由調用端操做
        //返回結果詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2 返回結果
        echo json_encode ($wx_result);
        exit;
    }
    
    
    
    
    //關閉訂單,詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_3
    public function closeorder(){
        $out_trade_no = $_POST ['out_trade_no']; // 商戶訂單號

        //獲取隨機字符串
        $nonce_str = $this->getNonceStr ();
    
        $sign ['appid']            = $this->wxpayconf ['appid'];
        $sign ['mch_id']           = $this->wxpayconf ['mch_id'];
        $sign ['out_trade_no']     = $out_trade_no;
        $sign ['nonce_str']        = $nonce_str;
        $sign ['sign']             = $this->getSign ( $sign, $this->wxpayconf ['key'] );
    
        // 先將數組換成XML格式
        $xmlData = $this->arrayToXml ( $sign );
        // 提交查詢訂單數據
        $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['closeorder_url'] );
        // 將返回信息從XML還原回數組
        $wx_result = $this->xmlToArray ( $xml_result );
        //返回結果詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_3 返回結果
        echo json_encode ($wx_result);
        exit;
    }
    
    
    
    //申請退款,詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4
    //退款需用到證書,在 postXmlCurl 方法中用到的證書在,微信商戶平臺(pay.weixin.qq.com)-->帳戶設置-->API安全-->證書下載 。證書文件有四個,這裏用三個,詳見 postXmlCurl 方法中證書調用
    function refund(){
        $out_trade_no  = $_POST ['out_trade_no']; // 訂單號
        $out_refund_no = $_POST ['out_refund_no']; // 商戶退款單號
        $total_fee     = $_POST ['total_fee']; // 訂單總金額,單位爲分
        $refund_fee    = $_POST ['refund_fee']; // 退款金額,單位爲分
        $notify_url    = $_POST ['notify_url'];//退款結果通知url
    
        //獲取隨機字符串
        $nonce_str = $this->getNonceStr ();
    
        $sign ['appid']            = $this->wxpayconf ['appid'];
        $sign ['mch_id']           = $this->wxpayconf ['mch_id'];
        $sign ['nonce_str']        = $nonce_str;
        $sign ['out_trade_no']     = $out_trade_no;
        $sign ['out_refund_no']    = $out_refund_no;
        $sign ['total_fee']        = $total_fee;
        $sign ['refund_fee']       = $refund_fee;
        $sign ['notify_url']       = $notify_url;
        $sign ['sign']             = $this->getSign ( $sign, $this->wxpayconf ['key'] );

        // 先將數組換成XML格式
        $xmlData = $this->arrayToXml ( $sign );
        // 提交退款申請
        $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['refund_url'], true );
        // 將返回信息從XML還原回數組
        $wx_result = $this->xmlToArray ( $xml_result );
        //返回結果詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4 返回結果
        echo json_encode ($wx_result);
        exit;
    }
    
    
    
    
    //退款查詢,詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_5
    function refundquery(){
        $out_refund_no = $_POST ['out_refund_no']; // 商戶退款單號

        //獲取隨機字符串
        $nonce_str = $this->getNonceStr ();
    
        $sign ['appid']            = $this->wxpayconf ['appid'];
        $sign ['mch_id']           = $this->wxpayconf ['mch_id'];
        $sign ['nonce_str']        = $nonce_str;
        $sign ['out_refund_no']    = $out_refund_no;
        $sign ['sign']             = $this->getSign ( $sign, $this->wxpayconf ['key'] );

        // 先將數組換成XML格式
        $xmlData = $this->arrayToXml ( $sign );
        // 提交退款申請
        $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['refundquery_url']);
        // 將返回信息從XML還原回數組
        $wx_result = $this->xmlToArray ( $xml_result );
        //返回結果詳見 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_5 返回結果
        echo json_encode ($wx_result);
        exit;
    }
    
    
    
    
    // 簽名方法
    function getSign($params, $key1) {
        // 簽名步驟一:按字典序排序數組參數
        ksort ( $params );
        $singstring = '';
        foreach ( $params as $key => $value ) {
            $singstring .= '&' . $key . '=' . $value;
        }
        $string = $singstring . "&key=" . $key1;
        // 簽名步驟三:MD5加密
        $string = ltrim ( $string, '&' );
        $string = md5 ( $string );
        // 簽名步驟四:全部字符轉爲大寫
        $result = strtoupper ( $string );
        return $result;
    }
    
    // 數組轉xml
    function arrayToXml($arr, $is_array = false) {
        if (! $is_array) {
            $xml = '<xml>';
        }
        foreach ( $arr as $key => $val ) {
            if (is_array ( $val )) {
                $xml .= "<" . $key . ">" . $this->arrayToXml ( $val, true ) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            }
        }
        if (! $is_array) {
            $xml .= "</xml>";
        }
        return $xml;
    }
    
    
    // 數組轉xml,備用,第一個很差用,用這個
    protected function arrayToXml_bak($arr) {
        $xml = "<xml>";
        foreach ( $arr as $key => $val ) {
            if (is_numeric ( $val )) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }
    
    // XML 轉數組
    protected function xmlToArray($xml) {
        $array_data = json_decode ( json_encode ( simplexml_load_string ( $xml, 'SimpleXMLElement', LIBXML_NOCDATA ) ), true );
        return $array_data;
    }
    
    // 發送xml請求方法,$verifyhost是否驗證主機證書,退款時須要驗證
    private static function postXmlCurl($xml, $url, $verifyhost = false, $second = 30) {
        $ch = curl_init ();
        curl_setopt ( $ch, CURLOPT_URL, $url );
        curl_setopt ( $ch, CURLOPT_HEADER, FALSE );
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, TRUE );
        // 是否須要證書驗證,支付時不須要,退款時須要驗證證書
        if($verifyhost == true){
            curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, TRUE);//證書檢查
            curl_setopt( $ch, CURLOPT_SSLCERTTYPE, 'pem');
            curl_setopt( $ch, CURLOPT_SSLCERT, '/var/www/html/xcxoauth.beauty-box.cn/public/cert/apiclient_cert.pem');
            curl_setopt( $ch, CURLOPT_SSLCERTTYPE, 'pem');
            curl_setopt( $ch, CURLOPT_SSLKEY, '/var/www/html/xcxoauth.beauty-box.cn/public/cert/apiclient_key.pem');
            //curl_setopt( $ch, CURLOPT_SSLCERTTYPE, 'pem');
            curl_setopt( $ch, CURLOPT_CAINFO, '/var/www/html/xcxoauth.beauty-box.cn/public/cert/rootca.pem');
        }else{
            curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
            curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, FALSE );
        }
        // post提交方式
        curl_setopt ( $ch, CURLOPT_POST, TRUE );
        curl_setopt ( $ch, CURLOPT_POSTFIELDS, $xml );
        curl_setopt ( $ch, CURLOPT_CONNECTTIMEOUT, 20 );
        // 設置超時
        curl_setopt ( $ch, CURLOPT_TIMEOUT, $second );
        set_time_limit ( 0 );
        // 運行curl
        $data = curl_exec ( $ch );
        // 返回結果
        if ($data) {
            curl_close ( $ch );
            return $data;
        } else {
            $error = curl_errno ( $ch );
            curl_close ( $ch );
            throw new WxPayException ( "curl出錯,錯誤碼:$error" );
        }
    }
    
    /*
     * 生成隨機字符串方法
     */
    protected function getNonceStr($length = 32) {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for($i = 0; $i < $length; $i ++) {
            $str .= substr ( $chars, mt_rand ( 0, strlen ( $chars ) - 1 ), 1 );
        }
        return $str;
    }
}
相關文章
相關標籤/搜索