1. 程式人生 > >thinkphp5實現根據渠道號不同實現安卓和IOS的APP支付和H5支付

thinkphp5實現根據渠道號不同實現安卓和IOS的APP支付和H5支付

<?php
namespace app\api\controller;
use think\Controller;
use app\common\model\ShopInfo as ShopInfoModel;
use app\common\model\UserOrderInfo as UserOrderInfoModel;
use app\common\model\User as UserModel;
use app\common\model\FromInfo  as FromInfoModel;

class Pay extends Controller
{
    public function _empty()
    {
        dump(input('get.'));die;
        return $this->fetch('/404');
    }

    //下單
    public function unifiedorder() {
        $playerid   = input('playerid/d');   //玩家id
        $shopid   = input('shopid/d');   //商品id
        $fromid   = input('fromid/d'); //渠道號
        $orderid = date('ymdHis', time()) . mt_rand(1000, 9999); //商戶唯一訂單號
        $shopinfo = ShopInfoModel::getOne('shopid, shopname, gamecoins, rmb', ['shopid' => $shopid]);
        if(empty($shopinfo)) {
            return json_return(-1, '商品不存在');
        }
        if(!$playerid) {
            return json_return(-1, '缺少玩家id');
        }
        
        //訂單資訊
        $total_fee = (config('environment') == 'test') ? 1 : $shopinfo->rmb * 100;//; //訂單金額
        $order = [
            'appid'     => config('system.appid'),
            'body'      => $shopinfo->shopname,
            'mch_id'    => config('system.mch_id'),
            'nonce_str' => uniqid(),
            'notify_url'  => config('system.site_url').url('pay/notify'),//接受微信非同步通知地址
            'out_trade_no' => $orderid,//商戶唯一訂單號,可包含字母序
            'spbill_create_ip'=>request()->ip(),//產生訂單號的伺服器IP
            'total_fee'   => $total_fee,//訂單金額,單位/分
        ];
        
        $frominfo = FromInfoModel::getOne('fromid, paytype', ['fromid' => $fromid]);
        if(empty($frominfo)) {
            return json_return(-1, '渠道號不存在');
        }
        $pay_type = $frominfo->paytype;
       
        if($fromid < 10000) {//ios
            switch ($pay_type) {
                case 1:
                    $order['trade_type'] = 'APP';
                    $order['appid'] = config('system.ios_appid');
                    break;
                case 2:
                    $order['trade_type'] = 'MWEB';
                    $order['scene_info'] = '{"h5_info": {"type":"IOS","app_name": "'.config('system.ios_appname').'","bundle_id": "'.config('system.ios_packagename').'"}}';
                    break;
                case 3:
                    $order['trade_type'] = 'ApplePay';
                    $orderRes = $this->createOrderInfo($fromid, $orderid, $playerid, $shopinfo, 3);
                    if($orderRes) {
                        $return_data =  [
                            'trade_type' => 'ApplePay', 
                            'orderid'    => $orderid 
                        ];
                        return json_return(0, 'success', $return_data);
                    }else{
                        return json_return(-1, '儲存訂單資訊失敗');
                    }
                    break;
                default:
                    return json_return(-1, '交易型別錯誤');
                    break;
            }
        }else if($fromid > 10000 && $fromid < 100000) {  //安卓
            switch ($pay_type) {
                case 1:
                    $order['trade_type'] = 'APP';
                    $order['appid'] = config('system.android_appid');
                    break;
                case 2:
                    $order['trade_type'] = 'MWEB';
                    $order['scene_info'] = '{"h5_info": {"type":"Android","app_name": "'.config('system.android_appname').'","package_name": "'.config('system.android_packagename').'"}}';
                    break;
                default:
                    return json_return(-1, '交易型別錯誤');
                    break;
            }
        }else if($fromid >= 100000) { //other
            $order['trade_type'] = 'MWEB';
            $order['scene_info'] = '{"h5_info": {"type":"IOS","app_name": "'.config('system.ios_appname').'","bundle_id": "'.config('system.ios_packagename').'"}}';
        }else{
            return json_return(-1, '當前裝置無法支付');
        }

        $response = $this->getWxUnifiedorderResponse($order);
        //若預下單成功,return_code 和result_code為SUCCESS。
        if ( $response['return_code'] ==='SUCCESS' && $response['result_code'] ==='SUCCESS') {
            //儲存訂單資訊
            $res = $this->createOrderInfo($fromid, $orderid, $playerid, $shopinfo);
            if($res) {
                //返回trade_type和prepay_id供前端呼叫
                $trade_type = $response['trade_type'];
                $return_data =  [
                    'trade_type' => $trade_type, 
                    'orderid'    => $orderid 
                ];
                switch ($trade_type) {
                    case 'APP':
                        $return_data['prepay_order'] = $this->getPrepayOrder($order['appid'], $response['prepay_id']);
                        break;
                    case 'MWEB':
                        $redirect_url = config('system.site_url').'/mweb.php' . '?mweb_url=xxx://xxx';  //後面的mweb_url為瀏覽器拉起應用的連結
                        $mweb_url = urlencode($response['mweb_url'].'&redirect_url='.$redirect_url);
                        $return_data['mweb_url'] = config('system.site_url').'/mweb.php' . '?mweb_url=' .$mweb_url;
                        //mweb_url為拉起微信支付收銀臺的中間頁面,可通過訪問該url來拉起微信客戶端,完成支付,mweb_url的有效期為5分鐘。
                        break;
                    default:
                        # code...
                        break;
                }
                return json_return(0, 'success', $return_data);
            }else{
                return json_return(-1, '儲存訂單資訊失敗');
            }
        }else{
            $errmsg = ($response['return_code'] == 'FAIL') ? $response['return_msg'] : $this->error_code($response['err_code']);
            return json_return(-1, $errmsg);
        }

    }
    
    /**
     * [createOrderInfo 建立訂單資訊]
     * @param  [integer]  $orderid  [訂單號]
     * @param  [integer]  $playerid [玩家id]
     * @param  [obj]  $shopinfo [商品資訊]
     * @param  integer $paytype  [支付型別]
     * @return [type]            [obj]
     */
    private function createOrderInfo($fromid, $orderid, $playerid, $shopinfo, $paytype = 1) {
        $userorderinfo = array(
            'orderid'   => $orderid,
            'shopname'  => $shopinfo->shopname,
            'gamecoins' => $shopinfo->gamecoins,
            'rmb'       => $shopinfo->rmb,
            'orderstatus' => 0,
            'fromid'   => $fromid,
            'paytype'  => $paytype, //支付方式1-微信,2-支付寶,3-Apple Pay
            'playerid' => $playerid,
            'shopid'   => $shopinfo->shopid,
            'orderdate'    => date('Y-m-d H:i:s'),
        );
        return UserOrderInfoModel::create($userorderinfo);
    }

    /**
     * [getWxUnifiedorderResponse 獲得微信下單結果]
     * @param  [array]  $orderid  [訂單資訊]
     * @return [array]      
     */
    private function getWxUnifiedorderResponse($order) {
        $sign = $this->getSign($order);
        $order['sign'] = $sign;
        //轉換成一維XML格式
        $xml = '<xml>';
        foreach($order as $k=>$v){
            $xml.='<'.$k.'><![CDATA['.$v.']]></'.$k.'>';
        }
        $xml.='</xml>';

        //CURL會話
        $response = curlHtml('https://api.mch.weixin.qq.com/pay/unifiedorder', $xml);
        //將xml格式的$response 轉成陣列
        $response = json_decode( json_encode( simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA) ), true );
        return $response;
    }

    //執行第二次簽名,才能返回給客戶端使用
    private function getPrepayOrder($appid, $prepayid){
        $data["appid"] = $appid;
        $data["noncestr"] = uniqid();
        $data["package"] = "Sign=WXPay";
        $data["partnerid"] = config('system.mch_id');
        $data["prepayid"] = $prepayid; //微信生成的預支付回話標識,用於後續介面呼叫中使用,該值有效期為2小時
        $data["timestamp"] = time();
        $data["sign"] = $this->getSign($data);

        return $data;
    }
    /**
     * 格式化引數格式化成url引數
     */
    private function ToUrlParams($data)
    {
        $buff = "";
        foreach ($data as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }
        
        $buff = trim($buff, "&");
        return $buff;
    }
    
    /**
     * 生成簽名
     * @return 簽名,本函式不覆蓋sign成員變數,如要設定簽名需要呼叫SetSign方法賦值
     */
    public function getSign($data)
    {
        //簽名步驟一:按字典序排序引數
        if(is_array($data)){
            ksort($data);
        } else{
            $data = [];
        }
        $string = $this->ToUrlParams($data);
        //簽名步驟二:在string後加入KEY
        $string = $string . "&key=".config('system.apikey');
        //簽名步驟三:MD5加密
        $string = md5($string);
        //簽名步驟四:所有字元轉為大寫
        $result = strtoupper($string);
        return $result;
    }
    
//訂單查詢
    public function order_query() {
        $orderid = input('orderid');
        require_once "./pay/wxpay/lib/WxPay.Api.php";
        $input = new \WxPayOrderQuery();
        $input->SetOut_trade_no($orderid);
        $result = \WxPayApi::orderQuery($input);
        if ($result['err_code_des'] == "order not exist") {
                // 訂單不存在
            $trade_state = '訂單不存在';
        } else {
            if ($result['trade_state'] == "SUCCESS") {
                $trade_state = '支付成功'; //支付成功
                return json_return(0, $trade_state, $result);
            } else if ($result['trade_state'] == "REFUND") {
                $trade_state = '已退款'; //已退款
            } else if ($result['trade_state'] == "NOTPAY") {
                $trade_state = '未支付'; //使用者還沒支付
            } else if ($result['trade_state'] == "CLOSED") {
                $trade_state = '訂單關閉'; //訂單關閉
            } else if ($result['trade_state'] == "REVOKED") {
                $trade_state = '已撤銷'; //已撤銷(刷卡支付)
            } else if ($result['trade_state'] == "USERPAYING") {
                $trade_state = '支付中'; //使用者支付中
            } else if ($result['trade_state'] == "PAYERROR") {
                $trade_state = '支付失敗'; //支付失敗(其他原因,例如銀行返回失敗)
            }
        }
        return json_return(-1, $trade_state);
    }

    //支付完成後的回撥
    public function notify()
    {
        $xml = file_get_contents('php://input');
        $result = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        //為了防止假資料,驗證簽名是否和返回的一樣。
        $sign = $this->getSign($result);
        if ( $sign === $result['sign']) {
            //  校驗返回的訂單金額是否與商戶側的訂單金額一致。修改訂單表中的支付狀態。
            if ($result['result_code'] == "SUCCESS" && $result['return_code'] == "SUCCESS") {
                //獲取訂單資訊
                $orderinfo = UserOrderInfoModel::getOne('orderid, rmb, playerid,shopid,orderstatus', ['orderid' => $result['out_trade_no']]);
                $total_fee = (config('environment') == 'test') ? 1 : $orderinfo->rmb * 100;//; //訂單金額
                if (($total_fee == $result['total_fee']) && ($orderinfo['orderstatus'] == 0)) {
                    $this->payHandler($orderinfo);
                }
            }
        }
        $return = ['return_code'=>'SUCCESS','return_msg'=>'OK'];
        $xml = '<xml>';
        foreach($return as $k=>$v){
            $xml.='<'.$k.'><![CDATA['.$v.']]></'.$k.'>';
        }
        $xml.='</xml>';

        echo $xml;
    }

    //支付成功後的處理
    private function payHandler($orderinfo) {
        UserOrderInfoModel::transaction(function() use($orderinfo){ //開啟事務
            //更新訂單等操作
        });
    }

    /**
     * 錯誤程式碼
     * @param  $code       伺服器輸出的錯誤程式碼
     * return string
     */
    private function error_code($code)
    {
        $errList = array(
            'NOAUTH'                => '商戶未開通此介面許可權',
            'NOTENOUGH'             => '使用者帳號餘額不足',
            'ORDERNOTEXIST'         => '訂單號不存在',
            'ORDERPAID'             => '商戶訂單已支付,無需重複操作',
            'ORDERCLOSED'           => '當前訂單已關閉,無法支付',
            'SYSTEMERROR'           => '系統錯誤!系統超時',
            'APPID_NOT_EXIST'       => '引數中缺少APPID',
            'MCHID_NOT_EXIST'       => '引數中缺少MCHID',
            'APPID_MCHID_NOT_MATCH' => 'appid和mch_id不匹配',
            'LACK_PARAMS'           => '缺少必要的請求引數',
            'OUT_TRADE_NO_USED'     => '同一筆交易不能多次提交',
            'SIGNERROR'             => '引數簽名結果不正確',
            'XML_FORMAT_ERROR'      => 'XML格式錯誤',
            'REQUIRE_POST_METHOD'   => '未使用post傳遞引數 ',
            'POST_DATA_EMPTY'       => 'post資料不能為空',
            'NOT_UTF8'              => '未使用指定編碼格式',
        );
        if (array_key_exists($code, $errList)) {
            return $errList[$code];
        }else{
            return $code;
        }
    }

}