1. 程式人生 > >小程式線上支付教程(二)

小程式線上支付教程(二)

小編推薦:Fundebug專注於JavaScript、微信小程式、微信小遊戲,Node.js和Java實時BUG監控。真的是一個很好用的bug監控費服務,眾多大佬公司都在使用。

上一章節我們講解了小程式線上支付的前期準備工作,這一章我們將講解如何編寫支付介面。之前我們也說了,我使用的是thinkphp5,因此希望大家在看我這篇文章的時候瞭解一下thinkphp5。

一、相關函式

/**
 * 密碼字符集
 * @param int $length
 * @return string
 */
public function generateNonceStr($length=16){
    // 密碼字符集,可任意新增你需要的字元
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    $str = "";
    for($i = 0; $i < $length; $i++)
    {
        $str .= $chars[mt_rand(0, strlen($chars) - 1)];
    }
    return $str;
}


//獲取IP
public function GetIP()
    {
        static $ip = NULL;
        if($ip !== NULL) return $ip;
        if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))  {
            $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $pos = array_search('unknown',$arr);
            if(false !== $pos) unset($arr[$pos]);
            $ip  = trim($arr[0]);
        }
        else if(isset($_SERVER['HTTP_CLIENT_IP']))  {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        }  else if(isset($_SERVER['REMOTE_ADDR']))  {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        //IP地址合法驗證
        $ip = (false !== ip2long($ip)) ? $ip : '0.0.0.0';
        return $ip;
    }
/**
 * 獲取訂單序號
 * @return string
 */
public function getOrder() {
    //訂單號碼主體(YYYYMMDDHHIISSNNNNNNNN)
    $order_id_main = date('YmdHis') . rand(10000000,99999999);
    //訂單號碼主體長度
    $order_id_len = strlen($order_id_main);
    $order_id_sum = 0;
    for($i=0; $i<$order_id_len; $i++){
        $order_id_sum += (int)(substr($order_id_main,$i,1));
    }
    //唯一訂單號碼(YYYYMMDDHHIISSNNNNNNNNCC)
    return $order_id_main . str_pad((100 - $order_id_sum % 100) % 100,2,'0',STR_PAD_LEFT);
}

 /**
     * 返回提示資訊
     * @param $code string 錯誤碼  4001 空值  4002 格式不正確  4003 長度  4004 提示  200正確放回 ,0失敗
     * @param $msg string 錯誤描述
     * @param $data string 返回值
     * @return array
     */
    public function resMap($code, $msg, $data) {
        $map = array();
        $map['errMsg'] = $msg;
        $map['data'] = $data;
        $map['errCode'] = $code;
        return json($map);
    }

二、簽名函式

  /**
     * 生成簽名, $KEY就是支付key
     * @return string 簽名
     */
    public function MakeSign($params,$KEY){
        //簽名步驟一:按字典序排序陣列引數
        ksort($params);
        $string = $this->ToUrlParams($params);  //引數進行拼接key=value&k=v
        Log::write('引數進行拼接:' .$string);
        //簽名步驟二:在string後加入KEY
        $string2 = $string . "&key=".$KEY;
        //簽名步驟三:MD5加密
        $string3 = md5($string2);
        //簽名步驟四:所有字元轉為大寫
        $result = strtoupper($string3);
        return $result;
    }

    /**
     * 將引數拼接為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;
    }

三、HTTP請求

 /**
     * 呼叫介面, $data是陣列引數
     * @return string 簽名
     */
    public function http_request($url,$data = null,$headers=array())
    {
        $curl = curl_init();
        if( count($headers) >= 1 ){
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($data)){
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        curl_close($curl);
        return $output;
    }

    //獲取xml裡面資料,轉換成array
    private function xml2array($xml){
        $p = xml_parser_create();
        xml_parse_into_struct($p, $xml, $vals, $index);
        xml_parser_free($p);
        $data = "";
        foreach ($index as $key=>$value) {
            if($key == 'xml' || $key == 'XML') continue;
            $tag = $vals[$value[0]]['tag'];
            $value = $vals[$value[0]]['value'];
            $data[$tag] = $value;
        }
        return $data;
    }

    /**
     * 將xml轉為array
     * @param $xml
     * @return bool|mixed
     */
    public function xml_to_array($xml){
        if(!$xml) {
            return false;
        }
        //將XML轉為array
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
    }

    public function post_data(){
        $receipt = $_REQUEST;
        if($receipt==null){
            $receipt = file_get_contents("php://input");
            if($receipt == null){
                $receipt = $GLOBALS['HTTP_RAW_POST_DATA'];
            }
        }
        return $receipt;
    }

在這裡,我要說明一下,在支付的時候我們需要商戶號和商戶金鑰,這兩樣可以登入微信支付平臺獲取,相關截圖如下:

 

商戶金鑰

商戶號

四、支付函式

  /**
     * 付款
     * @param $total_fee int 金額
     * @param $openid string 使用者OPENID
     * @param $order_id string 訂單號
     * @return array
     */
    public  function Pay($total_fee,$openid,$order_id){
        if(empty($total_fee)){
            return $this->resMap(4002,'金額有誤','金額有誤');
        }
        if(empty($openid)){
            return $this->resMap(4002,'登入失效,請重新登入','登入失效,請重新登入');
        }
        if(empty($order_id)){
            return $this->resMap(4002,'訂單不存在','訂單不存在');
        }
        $appid = 'xxx';//如果是公眾號 就是公眾號的appid;小程式就是小程式的appid
        $body = '訂單支付';
        $mch_id ='xxx'; //這是商戶號,登入微信支付平臺就能看到,參考教程一
        $KEY = 'xxxx'; //這裡是商戶的支付金鑰
        $nonce_str =    $this->generateNonceStr();//隨機字串
        $notify_url =   'https://xxxxxxx/rest/xiao_notify_url';  //支付完成回撥地址url,不能帶引數
        $out_trade_no = $order_id;//商戶訂單號
        $spbill_create_ip = $this->GetIP();
        $trade_type = 'JSAPI';//交易型別 預設JSAPI

        //這裡是按照順序的 因為下面的簽名是按照(字典序)順序 排序錯誤 肯定出錯
        $post['appid'] = $appid;
        $post['body'] = $body;
        $post['mch_id'] = $mch_id;
        $post['nonce_str'] = $nonce_str;//隨機字串
        $post['notify_url'] = $notify_url;
        $post['openid'] = $openid;
        $post['out_trade_no'] = $out_trade_no;
        $post['spbill_create_ip'] = $spbill_create_ip;//伺服器終端的ip
        $post['total_fee'] = intval($total_fee) * 100;        //總金額 最低為一分錢 必須是整數
        $post['trade_type'] = $trade_type;
        Log::write($post);
        $sign = $this->MakeSign($post,$KEY);              //簽名
        Log::write($sign); //列印日誌
        $post['sign'] = $sign;
        ksort($post); //簽名生成以後,還要進行一次排序,如果缺少這一步,你的簽名永遠都是“簽名錯誤”的提示,這裡我被坑過,PHP用的函式是ksort

        $post_xml = '<xml>
<appid>'.$post['appid'].'</appid>
<body>'.$post['body'].'</body>
<mch_id>'.$post['mch_id'].'</mch_id>
<nonce_str>'.$post['nonce_str'].'</nonce_str>
<notify_url>'.$post['notify_url'].'</notify_url>
<openid>'.$post['openid'].'</openid>
<out_trade_no>'.$post['out_trade_no'].'</out_trade_no>
<spbill_create_ip>'.$post['spbill_create_ip'].'</spbill_create_ip>
<total_fee>'.$post['total_fee'].'</total_fee><trade_type>'.$post['trade_type'].'</trade_type><sign>'.$post['sign'].'</sign></xml> ';
        Log::write($post_xml);//列印日誌
        //統一下單介面prepay_id
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $xml = $this->http_request($url,$post_xml);     //POST方式請求http
        $array = $this->xml2array($xml);               //將【統一下單】api返回xml資料轉換成陣列,全要大寫
        Log::write($array);
        if($array['RETURN_CODE'] == 'SUCCESS' && $array['RESULT_CODE'] == 'SUCCESS'){
            $time = time();
            $tmp='';                            //臨時陣列用於簽名
            $tmp['appId'] = $appid;
            $tmp['nonceStr'] = $nonce_str;
            $tmp['package'] = 'prepay_id='.$array['PREPAY_ID'];
            $tmp['signType'] = 'MD5';
            $tmp['timeStamp'] = "$time";
 //這裡的引數都是參考文件來的,地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
            $data['state'] = 1;
            $data['timeStamp'] =  "$time";           //時間戳
            $data['nonceStr'] = $nonce_str;         //隨機字串
            $data['signType'] = 'MD5';              //簽名演算法,暫支援 MD5
            $data['package'] = 'prepay_id='.$array['PREPAY_ID'];   //統一下單介面返回的 prepay_id 引數值,提交格式如:prepay_id=*
            $data['paySign'] = $this->MakeSign($tmp,$KEY);       //簽名,具體簽名方案參見微信公眾號支付幫助文件;
            $data['out_trade_no'] = $out_trade_no;
            $data['RETURN_CODE'] = $array['RETURN_CODE'];
            $data['RETURN_MSG'] = $array['RETURN_MSG'];
            Log::write($tmp);
            Log::write($data);
            return $this->resMap(200, $data, $data);
        }else{
            $data['state'] = 0;
            $data['text'] = "錯誤";
            $data['RETURN_CODE'] = $array['RETURN_CODE'];
            $data['RETURN_MSG'] = $array['RETURN_MSG'];
            return $this->resMap(4002, $data, $data);
        }

    }

五、回撥函式

當支付成功後,我們需要提供一個回撥函式,幫助我們更改訂單狀態

 /**
     * 支付成功後跳轉
     */
    public function xiao_notify_url() {
        $post = $this->post_data();    //接受POST資料XML個數
        $post_data = $this->xml_to_array($post);   //微信支付成功,返回回撥地址url的資料:XML轉陣列Array
        $postSign = $post_data['sign'];
        unset($post_data['sign']);
        ksort($post_data);// 對資料進行排序
        $str = $this->ToUrlParams($post_data);//對陣列資料拼接成key=value字串
        $user_sign = strtoupper(md5($str));   //再次生成簽名,與$postSign比較
        $out_trade_no = $post_data['out_trade_no'];
        Log::write('訂單好:' . $out_trade_no);
        $orderModel = model('Order');
        $res = $orderModel->getOrderMoney($out_trade_no);
        Log::write('支付成功後跳轉:' .$orderModel->getLastSql());
        if($post_data['return_code']=='SUCCESS'&&$postSign) {
            if ($res['g_state'] == 2) {
                return $this->return_success();
            } else {
                //修改訂單狀態
                $result = $orderModel->updateOrderStatus($out_trade_no,2);
                Log::write('支付成功後跳轉2:' .$orderModel->getLastSql());
                if ($result) {
                    //訂單更新成功
                    $this->return_success();
                } else {
                    return $this->resMap(4002,'訂單更新失敗','訂單更新失敗');
                }
            }
        } else {
            return $this->resMap(4002,'微信支付失敗','微信支付失敗');
        }
    }

    /*
    * 給微信傳送確認訂單金額和簽名正確,SUCCESS資訊 -xzz0521
    */
    public function return_success(){
        $return['return_code'] = 'SUCCESS';
        $return['return_msg'] = 'OK';
        $xml_post = '<xml>
                    <return_code>'.$return['return_code'].'</return_code>
                    <return_msg>'.$return['return_msg'].'</return_msg>
                    </xml>';
        return $this->resMap(200, $this->xml2array($xml_post), $this->xml2array($xml_post));
    }

以上就是支付介面的核心程式碼,下一章節我們將講解小程式中如何使用我們剛剛寫的介面,謝謝大家。


作者:陳楠酒肆
連結:https://www.jianshu.com/p/648be419c925

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了9億+錯誤事件,得到了Google、360、金山軟體、百姓網等眾多知名使用者的認可。歡迎免費試用!