微信掃碼支付Wxpay.class.php

在經(jīng)過微信掃碼支付DEMO的蹂躪后缭召,決定將繁復(fù)雜亂的DEMO文件重新整合封裝,最后就有了這個(gè)清晰簡潔的Wxpay.class.php文件了。

終極版 Wxpay.class.php
<?php
/**
 * Wxpay 自定義微信掃碼類
 * 主要是將支微信支付DEMO中的方法整合成了一個(gè)文件
 * @version 1.0 2016-07-06
 */
class Wxpay {

    // 交易類型 公眾號支付
    const TRADE_TYPE_JSAPI = 'JSAPI';
    // 交易類型 原生掃碼支付
    const TRADE_TYPE_NATIVE = 'NATIVE';
    // 交易類型 app支付
    const TRADE_TYPE_APP = 'APP';

    // 統(tǒng)一下單接口
    const URL_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    // 訂單查詢接口
    const URL_ORDER_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery";

    // 關(guān)單接口
    const URL_CLOSE_ORDER = 'https://api.mch.weixin.qq.com/pay/closeorder';

    // 申請退款接口
    const URL_REFUND = 'https://api.mch.weixin.qq.com/secapi/pay/refund';

    // 查詢退款接口
    const URL_REFUND_QUERY = 'https://api.mch.weixin.qq.com/pay/refundquery';

    // 下載對賬單接口
    const URL_DOWNLOAD_BILL = 'https://api.mch.weixin.qq.com/pay/downloadbill';

    // 交易保障接口
    const URL_REPORT = 'https://api.mch.weixin.qq.com/payitil/report';

    // 轉(zhuǎn)換短鏈接接口
    const URL_SHORT_URL = 'https://api.mch.weixin.qq.com/tools/shorturl';

    const URL_MICRO_PAY = 'https://api.mch.weixin.qq.com/pay/micropay';



    // 微信支付配置數(shù)組
    // appid        公眾賬號appid
    // mch_id       商戶號
    // apikey       加密key
    // appsecret    公眾號appsecret
    // sslcertPath  證書路徑(apiclient_cert.pem)
    // sslkeyPath   密鑰路徑(apiclient_key.pem)
    private $_config;


    /**
     * 構(gòu)造方法 初始化微信支付配置
     * @access public
     * @param $config 微信支付配置數(shù)組
     */
    public function __construct($config){

        $this->_config = $config;

    }

    /**
     * unifiedOrder 統(tǒng)一下單
     * @access public
     * @param array 統(tǒng)一下單參數(shù)數(shù)組
     * @return array
     */
    public function unifiedOrder($para = array()) {
        // out_trade_no 商戶系統(tǒng)訂單 必填
        if ( ! isset($para['out_trade_no'])) return false;
        // body 商品描述/訂單描述 必填
        if ( ! isset($para['body'])) return false;
        // total_fee 總金額 必填
        if ( ! isset($para['total_fee'])) return false;
        // spbill_create_ip 終端IP 必填
        if ( ! isset($para['spbill_create_ip'])) $para['spbill_create_ip'] = $_SERVER['REMOTE_ADDR'];
        // APPID 公眾賬號ID 必填
        $para['appid'] = $this->_config['APPID'];
        // MCHID 商戶號 必填
        $para['mch_id'] = $this->_config['MCHID'];
        // device_info 設(shè)備號
        if ( ! isset($para['device_info'])) $para['device_info'] = 'WEB';
        // nonce_str 32位隨機(jī)字符串
        $para['nonce_str'] = $this->getNonceStr();
        // detail 商品詳情 可選
        if ( ! isset($para['detail'])) $para['detail'] = '';
        // attach 附加數(shù)據(jù) 可選
        if ( ! isset($para['attach'])) $para['attach'] = '';
        // fee_type 貨幣類型 可選
        if ( ! isset($para['fee_type'])) $para['fee_type'] = 'CNY';
        // time_start 交易起始時(shí)間 可選
        if ( ! isset($para['time_start'])) $para['time_start'] = '';
        // time_expire 交易結(jié)束時(shí)間 可選
        if ( ! isset($para['time_expire'])) $para['time_expire'] = '';
        // goods_tag 商品標(biāo)記 可選
        if ( ! isset($para['goods_tag'])) $para['goods_tag'] = '';
        // notify_url 通知地址 必填
        if ( ! isset($para['notify_url'])) $para['notify_url'] = $this->_config['NOTIFY_URL'];
        // trade_type 交易類型
        if ( ! isset($para['trade_type'])) $para['trade_type'] = 'NATIVE';
        if ($para['trade_type'] == 'NATIVE') {
            // product_id 商品ID 自定義 trade_type=NATIVE時(shí) 必填
            if ( ! isset($para['product_id'])) $para['product_id'] = $this->_config['PRODUCT_ID'];
            if ( ! isset($para['openid'])) $para['openid'] = '';
        }
        if ($para['trade_type'] == 'JSAPI') {
            if ( ! isset($para['product_id'])) $para['product_id'] = '';
            // openid 用戶標(biāo)識 trade_type=JSAPI時(shí) 必填
            if ( ! isset($para['openid'])) return false;
        }
        
        return $this->getHttpResponsePOST(self::URL_UNIFIED_ORDER, $para);
    }

    /**
     * getCodeUrl 掃碼支付 模式二 獲取支付二維碼
     * @access public
     * @param array $para 支付參數(shù)數(shù)組
     * @return string
     */
    public function getCodeUrl($para = array()){
        $r = $this->unifiedOrder($para);
        if ($r['return_code'] == 'SUCCESS') {
            if ($r['result_code'] == 'SUCCESS') {
                return $r['code_url'];
            } else {
                $this->logDebug('#獲取支付CODE_URL錯(cuò)誤# '.$r['err_code'].' : '.$r['err_code_des']);
                return false;
            }
        } else {
            $this->logDebug('#獲取支付CODE_URL錯(cuò)誤# '.$r['return_msg']);
            return false;
        }
    }

    /**
     * orderQuery 查詢訂單
     * @access public
     * @param string $order_code
     * @param bool $mode
     * @return array
     */
    public function orderQuery($order_code, $mode = false){
        $para = array();
        $para['appid'] = $this->_config['APPID'];
        $para['mch_id'] = $this->_config['MCHID'];
        $para['nonce_str'] = $this->getNonceStr();
        if ($mode) {    // 使用微信訂單號
            $para['transaction_id'] = $order_code;
        } else {    // 使用商戶訂單號
            $para['out_trade_no'] = $order_code;
        }

        return $this->getHttpResponsePOST(self::URL_ORDER_QUERY, $para);
    }

    /**
     * closeOrder 關(guān)閉訂單
     * @access public
     * @param string $out_trade_no
     * @return array
     */
    public function closeOrder($out_trade_no){
        $para = array(
            'appid'        => $this->_config['APPID'],
            'mch_id'       => $this->_config['MCHID'],
            'nonce_str'    => $this->getNonceStr(),
            'out_trade_no' => $out_trade_no
        );

        return $this->getHttpResponsePOST(self::URL_CLOSE_ORDER, $para);
    }

    /**
     * refund 申請退款
     * @access public
     * @param array $para 參數(shù)數(shù)組
     * @param bool $mode
     * @return array
     */
    public function refund($para = array(), $mode = false){
        $para['appid'] = $this->_config['APPID'];
        $para['mch_id'] = $this->_config['MCHID'];
        $para['nonce_str'] = $this->getNonceStr();
        if ($mode) {    // 使用微信訂單號退款
            if ( ! isset($para['transaction_id'])) return false;
        } else {    // 使用商戶訂單號退款
            if ( ! isset($para['out_trade_no'])) return false;
        }
        if ( ! isset($para['out_refund_no'])) return false;
        if ( ! isset($para['total_fee'])) return false;
        if ( ! isset($para['refund_fee'])) return false;
        if ( ! isset($para['op_user_id'])) $para['op_user_id'] = $this->_config['MCHID'];

        return $this->getHttpResponsePOST(self::URL_REFUND, $para, true);
    }

    /**
     * downloadBill 下載對賬單
     * @access public
     * @param string $bill_date 下載對賬單的日期,格式:20140603
     * @param string $bill_type 類型
     * @return array
     */
    public function downloadBill($bill_date, $bill_type = 'ALL'){
        $para = array();
        $para['appid'] = $this->_config['APPID'];
        $para['mch_id'] = $this->_config['MCHID'];
        $para['nonce_str'] = $this->getNonceStr();
        $para['bill_date'] = $bill_date;
        $para['bill_type'] = $bill_type;

        return $this->getHttpResponsePOST(self::URL_DOWNLOAD_BILL, $para);
    }

    /**
     * 獲取js支付使用的第二個(gè)參數(shù)
     */
    public function get_package($prepay_id) {
        $data = array();
        $data['appId'] = $this->_config['APPID'];
        $data['nonceStr']  = $this->getNonceStr();
        $data['timeStamp'] = time();
        $data['package']   = 'prepay_id='.$prepay_id;
        $data['signType']  = 'MD5';
        $data['paySign']   = $this->sign($data);
        
        return $data;
    }


    /**
     * getWxpayBackData 獲取微信返回?cái)?shù)據(jù)
     * @access public
     * @param void
     * @return array | bool
     */
    public function getWxpayBackData() {
        $xml = file_get_contents('php://input');
        $data = $this->xml2array($xml);

        return $this->checkSign($data) ? $data : NULL;
    }

    /**
     * replyNotify 回復(fù)通知
     * @access public
     * @param string $return_code 返回狀態(tài)碼
     * @param string $return_msg 返回信息
     * @return void
     */
    final public function replyNotify($return_code = 'SUCCESS', $return_msg = ''){
        $data = array();
        $data['return_code'] = $return_code;
        if ($return_msg) $data['return_msg'] = $return_msg;

        echo $this->array2xml($data);
    }

    /**
     * sign 生成簽名
     * @access private
     * @param array $data
     * @return string
     */
    private function sign($data = array()){
        // 簽名步驟一 按字典序排序參數(shù)
        ksort($data);

        $buff = '';

        // 去除空值
        // 把數(shù)組所有元素 按照“參數(shù)=參數(shù)值”的模式用“&”字符拼接成字符串
        // 簽名步驟二 格式化參數(shù)格式化成url參數(shù)
        foreach ($data as $k => $v) {
            if($k != 'sign' && $v != '' && ! is_array($v)){
                $buff .= $k . '=' . $v . '&';
            }
        }
        $buff = trim($buff, '&');

        // 簽名步驟三 在buff后加入KEY
        $string_sign_temp = $buff . '&key=' . $this->_config['KEY'];

        return strtoupper(md5($string_sign_temp));
    }

    /**
     * checkSign 驗(yàn)證數(shù)據(jù)簽名
     * @access public
     * @param array $data 數(shù)據(jù)數(shù)組
     * @return bool 數(shù)據(jù)校驗(yàn)結(jié)果
     */
    public function checkSign($data) {
        if ( ! isset($data['sign']))  return false;
        $sign = $data['sign'];
        unset($data["sign"]);

        return $this->sign($data) == $sign;
    }

    /**
     * array2xml 將數(shù)組轉(zhuǎn)換為XML
     * @access private
     * @param array $arr
     * @return xml
     */
    private function array2xml($arr = array()){
        $xml = '<xml>' . PHP_EOL;
        foreach ($arr as $k => $v) {
            if ($v > 0 && trim($v) != '' && ! is_array($v)) {
                if (is_numeric($v)) {
                    $xml.='<'.$k.'>'.$v.'</'.$k.'>' . PHP_EOL;
                }else{
                    $xml.='<'.$k.'><![CDATA['.$v.']]></'.$k.'>' . PHP_EOL;
                }
            }
        }
        $xml .= '</xml>';

        return $xml;
    }

    /**
     * xml2array 將XML轉(zhuǎn)換為數(shù)組
     * @access private
     * @param string $xml
     * @return array $arr
     */
    private function xml2array($xml) {
        try {
            return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        } catch(Exception $e) {

        }
    }

    /**
     * getNonceStr 產(chǎn)生隨機(jī)字符串庐舟,不長于32位
     * @access public
     * @param void
     * @return 產(chǎn)生的隨機(jī)字符串
     */
    public function getNonceStr($len = 32){

        return substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, $len);

    }


    /**
     * getHttpResponsePOST 遠(yuǎn)程獲取數(shù)據(jù) POST模式
     * @access private
     * @param string $url
     * @param array $data
     * @param bool $is_need_cert
     * @return array
     */
    private function getHttpResponsePOST($url, $data = array(), $is_need_cert = false){
        $data['sign'] = $this->sign($data);
        $xml = $this->array2xml($data);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_URL, $url);
        if ($is_need_cert == true) {
            //使用證書 cert 與 key 分別屬于兩個(gè).pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, $this->_config['SSLCERT_PATH']);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, $this->_config['SSLKEY_PATH']);
        }
        $content = curl_exec($ch);
        $array = $this->xml2array($content);
        return $array;
    }

    /**
     * logDebug
     * 寫日志纽乱,方便測試(看網(wǎng)站需求幸缕,也可以改成把記錄存入數(shù)據(jù)庫)
     * 注意:服務(wù)器需要開通fopen配置
     * @access public
     * @param string $content 要寫入日志里的文本內(nèi)容 默認(rèn)值:空值
     * @return void
     */
    public function logDebug($content = '') {
        $fp = fopen($this->_config['LOG_PATH'], 'a+');
        flock($fp, LOCK_EX);    // 文件鎖定 避免其他人同時(shí)操作
        fwrite($fp, '# Wxpay Debug : ' . date('Y-m-d H:i:s') . ' # '. $content . "\n\n");
        flock($fp, LOCK_UN);
        fclose($fp);
    }


}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悔详,一起剝皮案震驚了整個(gè)濱河市涵亏,隨后出現(xiàn)的幾起案子宰睡,更是在濱河造成了極大的恐慌,老刑警劉巖气筋,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拆内,死亡現(xiàn)場離奇詭異,居然都是意外死亡宠默,警方通過查閱死者的電腦和手機(jī)麸恍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搀矫,“玉大人抹沪,你說我怎么就攤上這事“” “怎么了采够?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長冰垄。 經(jīng)常有香客問我蹬癌,道長权她,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任逝薪,我火速辦了婚禮隅要,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘董济。我一直安慰自己步清,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布虏肾。 她就那樣靜靜地躺著廓啊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪封豪。 梳的紋絲不亂的頭發(fā)上谴轮,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機(jī)與錄音吹埠,去河邊找鬼第步。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缘琅,可吹牛的內(nèi)容都是我干的粘都。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼刷袍,長吁一口氣:“原來是場噩夢啊……” “哼翩隧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起做个,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤鸽心,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后居暖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顽频,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年太闺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糯景。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡省骂,死狀恐怖蟀淮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钞澳,我是刑警寧澤怠惶,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站轧粟,受9級特大地震影響策治,放射性物質(zhì)發(fā)生泄漏脓魏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一通惫、第九天 我趴在偏房一處隱蔽的房頂上張望茂翔。 院中可真熱鬧,春花似錦履腋、人聲如沸珊燎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悔政。三九已至,卻和暖如春延旧,著一層夾襖步出監(jiān)牢的瞬間卓箫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工垄潮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闷盔。 一個(gè)月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓弯洗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逢勾。 傳聞我的和親對象是個(gè)殘疾皇子牡整,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評論 2 350

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,849評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件溺拱、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,066評論 4 62
  • 從今天開始我要記錄我每天的收獲迫摔,養(yǎng)成寫日記的習(xí)慣沐扳,
    巧蕾陪你一起去看海閱讀 196評論 0 1
  • 也許平時(shí)里表現(xiàn)最樂觀的人,受到的傷害也是最深的句占。 我自己是一個(gè)很樂觀的人沪摄,甚至在許多同學(xué)和朋友的心里我可能...
    WIFIgawaine閱讀 162評論 0 0
  • 我都不知道該說什么了,不是我不努力去改善纱烘,而是實(shí)在是……或許我們都太自我為中心了杨拐,我得不到尊重,我選擇躲避擂啥,可以躲...
    正一爸閱讀 251評論 0 0