PHP 微信小程序支付api及回調

下面是控制器Pay.php

<?php
namespace app\wechatapi\controller;
use app\wechatapi\controller\Base;
use think\Db;
use think\Controller;
use \app\wechatapi\model;
class Pay extends Base {
    //吊起微信支付
    public function getPrepare(){
        $model=new model\Pay;
        $result=$model->unifiedorder('111335555222','o-9fT5I1Zqqe2SYwNeZI0N_GQzFs',1);
        var_dump($result);
    }
    //微信支付回調
    public function notify(){
        file_put_contents("wxback.txt",file_get_contents("php://input"));
        $postStr = file_get_contents('php://input');
        $post_data = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
        Db::table('hotel_option_log')->insert(['option'=>json_encode($post_data),'type'=>'微信支付回調']);
        /**
         * 解析出來的數組
         *Array
         * (
         * [appid] => wx1c870c0145984d
         * [bank_type] => CFT
         * [cash_fee] => 100
         * [fee_type] => CNY
         * [is_subscribe] => N
         * [mch_id] => 12972103
         * [nonce_str] => gkq1x5fxejqo5lz5eua50gg4c4la18vy
         * [openid] => olSGW5BBvfep9UhlU40VFIQlcvZ0
         * [out_trade_no] => 56265623
         * [result_code] => SUCCESS
         * [return_code] => SUCCESS
         * [sign] => F6890323B0A6A3765510D152D9420EAC
         * [time_end] => 20180626170839
         * [total_fee] => 100
         * [trade_type] => JSAPI
         * [transaction_id] => 4200000134201806265483331660
         */
        if($post_data != null && $post_data['return_code'] === 'SUCCESS'&& !empty($post_data)){
            $out_trade_no=$post_data['out_trade_no'];
            $order=Db::table('order')->where(['order_id'=>$out_trade_no,'all_price'=>($post_data['total_fee']/100),'order_status'=>1])->select();
            if(!empty($order)){
                //修改數據庫
                $up_data=Db::table('order')->where(['order_id'=>$out_trade_no])->update(['out_order_id'=>$post_data['transaction_id'],'pay_status'=>1,'order_status'=>2,'pay_time'=>$post_data['time_end']]);
                if($up_data){
                    $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
                    echo $str;
                    exit('回調成功');
                }else{
                    $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[修改訂單狀態失敗]]></return_msg></xml>';
                    echo $str;
                    exit('回調失敗');
                }
            }
        }else{
            $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>';
            echo $str;
            exit('回調失敗');
        }
    }
}

下面是model pay.php

<?php
namespace app\wechatapi\model;

use think\Model;

class Pay extends Model
{
    //支付model

        const app_id = ''; //小程序appid
        const mchid = ''; //商戶號
        const apikey= '';  //支付祕鑰

    public function unifiedorder($order_sn, $openid, $total_fee)
    {
        // 當前時間
        $time = time();
        // 生成隨機字符串
        $nonceStr = md5($time . $openid);
        // API參數
        $params = [
            'appid' => self::app_id,
            'attach' => 'test',
            'body' => $order_sn,
            'mch_id' => self::mchid,
            'nonce_str' => $nonceStr,
            'notify_url' => 'https://'.$_SERVER['SERVER_NAME'].url('/wechatapi/pay/notify'),  // 異步通知地址
            'openid' => $openid,
            'out_trade_no' => $order_sn,
            'spbill_create_ip' => \request()->ip(),
            'total_fee' => $total_fee * 100, // 價格:單位分
            'trade_type' => 'JSAPI',
        ];
        // 生成簽名
            $params['sign'] = $this->makeSign($params);
        // 請求API
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $result = $this->postXmlCurl($this->toXml($params), $url);

        $prepay = $this->fromXml($result);
        // 請求失敗
        if ($prepay['return_code'] === 'FAIL') {
            return $this->renderError($prepay['err_code_des']);
        }
        if ($prepay['result_code'] === 'FAIL') {
            return $this->renderError($prepay['err_code_des']);
        }
        // 生成 nonce_str 供前端使用
        $paySign = $this->makePaySign($params['nonce_str'], $prepay['prepay_id'], $time);
        return [
            'nonceStr' => $nonceStr,
            'package' => "prepay_id=".$prepay['prepay_id'],
            'paySign' => $paySign,
            'signType'=>'MD5',
            'timeStamp' => (string)$time
        ];
    }

    /**
     * 以post方式提交xml到對應的接口url
     * @param $xml
     * @param $url
     * @param int $second
     * @return mixed
     */
    public function postXmlCurl($xml, $url, $second = 30){
        $ch = curl_init();
        // 設置超時
        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, FALSE);//嚴格校驗
        // 設置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        // 要求結果爲字符串且輸出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        // 運行curl
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    /**
     * 輸出xml字符
     * @param $values
     * @return bool|string
     */
    public function toXml($values)
    {
        if (!is_array($values)
            || count($values) <= 0
        ) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 將xml轉爲array
     * @param $xml
     * @return mixed
     */
    public function fromXml($xml)
    {
        // 禁止引用外部xml實體
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * 生成簽名
     * @param $values
     * @return string 本函數不覆蓋sign成員變量,如要設置簽名需要調用SetSign方法賦值
     */
    public function makeSign($values){
        //簽名步驟一:按字典序排序參數
        ksort($values);
        $string = $this->toUrlParams($values);
        //簽名步驟二:在string後加入KEY
        $string = $string . '&key=' . self::apikey;
        //簽名步驟三:MD5加密
        $string = md5($string);
        //簽名步驟四:所有字符轉爲大寫
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 生成paySign
     * @param $nonceStr
     * @param $prepay_id
     * @param $timeStamp
     * @return string
     */
    public function makePaySign($nonceStr, $prepay_id, $timeStamp)
    {
        $data = [
            'appId' => self::app_id,
            'nonceStr' => $nonceStr,
            'package' => 'prepay_id=' . $prepay_id,
            'signType' => 'MD5',
            'timeStamp' => $timeStamp,
        ];

        //簽名步驟一:按字典序排序參數
        ksort($data);

        $string = $this->toUrlParams($data);

        //簽名步驟二:在string後加入KEY
        $string = $string . '&key=' . self::apikey;

        //簽名步驟三:MD5加密
        $string = md5($string);

        //簽名步驟四:所有字符轉爲大寫
        $result = strtoupper($string);

        return $result;
    }

    /**
     * 格式化參數格式化成url參數
     * @param $values
     * @return string
     */
    public function toUrlParams($values)
    {
        $buff = '';
        foreach ($values as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k . '=' . $v . '&';
            }
        }
        return trim($buff, '&');
    }


    /**
     * 支付通知
     */
    public function  notify()
    {
        if (!$xml = file_get_contents('php://input')) {
            $this->returnCode(false, 'Not found DATA');
        }
        // 將服務器返回的XML數據轉化爲數組
        $data = $this->fromXml($xml);
        // 保存微信服務器返回的簽名sign
        $dataSign = $data['sign'];
        // sign不參與簽名算法
        unset($data['sign']);
        // 生成簽名
        $sign = $this->makeSign($data);
        $wx_total_fee = $data['total_fee'];
        // 判斷簽名是否正確  判斷支付狀態
        if (($sign === $dataSign) && ($data['return_code'] == 'SUCCESS') && ($data['result_code'] == 'SUCCESS')) {
            $order_sn = $data['out_trade_no'];
            $order_amount = M('order')->where(['master_order_sn'=>"$order_sn"])->whereOr(['order_sn'=>"$order_sn"])->sum('order_amount');
            if ((string)($order_amount * 100) == (string)$wx_total_fee) {
                //更新訂單狀態,自身邏輯,不做贅述

            }
        }
    }
}

 

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