Sivo 悉见

APP支付(支付宝和微信生成客户端签名及回调验签)

匿名 · 更新于 2017/6/29

[php] view plain copy
 
 print?
  1. <?php  
  2. namespace Pn\Controller;  
  3. use Think\Controller\RestController;  
  4. class PayController extends RestController{  
  5.     public function __construct(){  
  6.         import('Vendor.WePay.WeEncryption');  
  7.         import('Vendor.WePay.Curl');  
  8.     }  
  9.     public function Payment(){  
  10.         $arr['appid'] = $_POST['appid'];//应用id  
  11.         $arr['userdid'] = $_POST['userdid'];//登录用户id  
  12.         $arr['paytype'] = $_POST['paytype'];//支付方式  
  13.         $arr['productid'] = $_POST['productid'];//商品id  
  14.         $arr['time'] = $_POST['time'];  
  15.         $sign = $_POST['sign'];//安全验证sign  
  16.         $vsign = strrank($arr);//签名加密  
  17.           
  18.         if($vsign!=$sign){  
  19.             $return['respCode'] = 1001;  
  20.             $return['msg'] = "签名错误";  
  21.             $this -> response($return,'json');//返回数据  
  22.             exit();  
  23.         }else{  
  24.             switch ($_POST["paytype"]){  
  25.                 case 1://支付宝  
  26.                     $data["signdata"] = $this->alipay();  
  27.                     echo json_encode($data);  
  28.                 break;  
  29.                 case 2://微信支付  
  30.                     $data = $this->wechatpay($_POST['appid'],$_POST['userdid'],$_POST['productid']);  
  31.                     echo $data;  
  32.                 break;  
  33.                 default:  
  34.                 $return["respCode"] = "error";   
  35.                 $return['msg'] = "支付方式错误";  
  36.                 $this -> response($return,'json');//返回数据  
  37.                 exit();  
  38.             }  
  39.         }  
  40.     }  
  41.     /** 
  42.      * 微信APP客户端支付 
  43.      */  
  44.     public function wechatpay($appid,$userdid,$productid){  
  45.        $WeEncryption = new \WeEncryption(); //实例化传输类;  
  46.        switch ($productid){  
  47.            case 1://季付商品(¥100)  
  48.                $body = "陪你季付VIP";//商品描述  
  49.                $total_fee = "100.00";//总金额  
  50.            break;  
  51.            case 2://月付商品(¥50)  
  52.                $body = "陪你月付VIP";//商品描述  
  53.                $total_fee = "50.00";//总金额  
  54.            break;  
  55.        }  
  56.        $out_trade_no = date("YmdHis").rand(10,100);//商户订单号  
  57.        $spbill_create_ip = getclientip();//终端IP  
  58.   
  59.        $data = array(  
  60.            'body'               =>   $body,  
  61.            'out_trade_no'       =>   $out_trade_no,  
  62.            'total_fee'          =>   $total_fee,  
  63.            'spbill_create_ip'   =>   $spbill_create_ip,  
  64.        );  
  65.          
  66.        $encpt = $WeEncryption::getInstance();   //实例化签名类  
  67.        $url = "http://xxxx.com/Pay/wx_notify_verify";  
  68.        $encpt->setNotifyUrl($url);           //设置异步通知地址  
  69.          
  70.        $curl = new \Curl();             //实例化传输类;  
  71.        $xml_data = $encpt->sendRequest($curl$data);        //发送请求  
  72.   
  73.        $postObj = $encpt->xmlToObject($xml_data);            //解析返回数据  
  74.        if ($postObj === false) {  
  75.            echo 'FAIL';  
  76.            exit;  
  77.        }  
  78.        if ($postObj->return_code == 'FAIL') {  
  79.            echo $postObj->return_msg;  
  80.        } else {  
  81.            $resignData = array(  
  82.                'appid'          =>   $postObj->appid,  
  83.                'partnerid'      =>   $postObj->mch_id,  
  84.                'prepayid'       =>   $postObj->prepay_id,  
  85.                'noncestr'       =>   $postObj->nonce_str,  
  86.                'timestamp'      =>   time(),  
  87.                'package'    =>   'Sign=WXPay'  
  88.            );  
  89.            $sign = $encpt->getClientPay($resignData);  
  90.            $resignData['sign'] = $sign;  
  91.            return  json_encode($resignData);  
  92.        }  
  93.     }  
  94.     /** 
  95.      * 微信App支付回调 
  96.      */  
  97.     public function wx_notify_verify(){  
  98.         $WeEncryption = new \WeEncryption();    //实例化传输类;  
  99.         $encpt = $WeEncryption::getInstance();  
  100.         $obj = $encpt->getNotifyData();  
  101.         if ($obj === false) {  
  102.             exit;  
  103.         }  
  104.         if ($obj) {  
  105.             $data = array(  
  106.                 'appid'             =>   $obj->appid,  
  107.                 'mch_id'            =>   $obj->mch_id,  
  108.                 'nonce_str'         =>   $obj->nonce_str,  
  109.                 'result_code'       =>   $obj->result_code,  
  110.                 'openid'            =>   $obj->openid,  
  111.                 'trade_type'        =>   $obj->trade_type,  
  112.                 'bank_type'         =>   $obj->bank_type,  
  113.                 'total_fee'         =>   $obj->total_fee,  
  114.                 'cash_fee'          =>   $obj->cash_fee,  
  115.                 'transaction_id'    =>   $obj->transaction_id,  
  116.                 'out_trade_no'      =>   $obj->out_trade_no,  
  117.                 'time_end'          =>   $obj->time_end  
  118.             );  
  119.             $sign = $encpt->getSign($data);  
  120.             if ($sign == $obj->sign) {//验签通过  
  121.                 $reply = "<xml>  
  122.                     <return_code><![CDATA[SUCCESS]]></return_code>  
  123.                     <return_msg><![CDATA[OK]]></return_msg>  
  124.                 </xml>";  
  125.                 echo $reply;  
  126.                 exit;  
  127.             }else{  
  128.                 $reply = "<xml>  
  129.                     <return_code><![CDATA[FAIL]]></return_code>  
  130.                     <return_msg><![CDATA[NO]]></return_msg>  
  131.                 </xml>";  
  132.                 echo $reply;  
  133.                 exit;  
  134.             }  
  135.         }  
  136.     }  
  137.       
  138.     /** 
  139.      * https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.NgdeQA&treeId=59&articleId=103663&docType=1 
  140.      * 服务端生成app支付使用的参数以及签名 
  141.      * @param $params <Array> 
  142.      * @return <Array> 
  143.     */  
  144.     public function alipay(){   
  145.   
  146.         $ali = array(  
  147.           'service' => 'mobile.securitypay.pay',  
  148.           'partner' => 'xxxx',  
  149.           '_input_charset' => 'utf-8',  
  150.           'sign_type' => 'RSA',  
  151.           'sign' => '',  
  152.           'notify_url' => 'http://xxxxxxxx.com/Pay/alipay_notify_url',//回调地址  
  153.           'out_trade_no' => date("YmdHis").rand(10,100),//商户网站唯一订单号  
  154.           'subject' => '测试',//商品名称  
  155.           'payment_type' => 1,//支付类型  
  156.           'seller_id' => 'lql@jieku.com',//支付宝账号  
  157.           'total_fee' => '0.01',//总金额  
  158.           'body' => 'Iphone6 16G',//商品详情  
  159.         );  
  160.           
  161.         $ali = self::argSort($ali);  
  162.         $str = '';  
  163.           
  164.         foreach($ali as $key=>$val){  
  165.             if($key == 'sign_type' || $key == 'sign'){  
  166.                 continue;  
  167.             }else{  
  168.                 if($str == ''){  
  169.                     $str = $key.'='.'"'.$val.'"';  
  170.                 }else{  
  171.                     $str = $str.'&'.$key.'='.'"'.$val.'"';  
  172.                 }  
  173.             }  
  174.         }          
  175.                                  
  176.         $sign = urlencode(self::sign($str));  
  177.         $str = $str.'&sign='.'"'.$sign.'"'.'&sign_type='.'"'.$ali['sign_type'].'"';//传给支付宝接口的数据  
  178.         return $str;  
  179.     }  
  180.       
  181.     /** 
  182.      * 准备签名参数 
  183.      */  
  184.     public function argSort($para) {  
  185.         ksort($para);  
  186.         reset($para);  
  187.         return $para;  
  188.     }  
  189.       
  190.    /** 
  191.      * 生成请求参数的签名 
  192.      * @param $params <Array> 
  193.      * @return <String> 
  194.      * 
  195.    */  
  196.    public function sign($data) {  
  197.         //读取私钥文件  
  198.         $priKey = file_get_contents('./Public/key/rsa_private_key.pem');//私钥文件路径  
  199.         //转换为openssl密钥,必须是没有经过pkcs8转换的私钥  
  200.         $res = openssl_get_privatekey($priKey);  
  201.         //调用openssl内置签名方法,生成签名$sign  
  202.         openssl_sign($data$sign$res);  
  203.         //释放资源  
  204.         openssl_free_key($res);  
  205.         //base64编码  
  206.         $sign = base64_encode($sign);  
  207.         return $sign;  
  208.     }  
  209.       
  210.    /** 
  211.      * 支付宝回调地址 
  212.     */  
  213.     public function alipay_notify_url(){  
  214.   
  215.         $orderpay = M("orderpay");  
  216.         $async = empty($_GET);  
  217.         $data = $async ? $_POST : $_GET;  
  218.     
  219.         if (empty($data)) {  
  220.             return FALSE;  
  221.         }  
  222.   
  223.         $signValid = $this->verifyParameters($data$data["sign"]);//验签  
  224.         $notify_id = isset($data['notify_id']) ? $data['notify_id'] : NULL;  
  225.           
  226.         //获取支付宝远程服务器ATN结果(验证是否是支付宝发来的消息)  
  227.         $responseTxt = 'true';  
  228.         if (!empty($notify_id)) {  
  229.             $responseTxt = $this->verifyFromServer($notify_id);  
  230.         }  
  231.   
  232.         if($signValid && preg_match("/true$/i"$responseTxt)){//验证成功  
  233.             if($_POST['trade_status'] == 'TRADE_FINISHED'){  
  234.                   
  235.                $_POST["createdatetime"] = time();  
  236.                  
  237.                if($orderpay->create()){  
  238.                     $orderpay->add();  
  239.                     echo "success";  
  240.                }else{  
  241.                    echo "fail";//验证失败  
  242.                }  
  243.             }  
  244.             else if ($_POST['trade_status'] == 'TRADE_SUCCESS') {  
  245.                 $_POST["createdatetime"] = time();  
  246.                 if($orderpay->create()){  
  247.                     $orderpay->add();  
  248.                     echo "success";  
  249.                 }else{  
  250.                     echo "fail";//验证失败  
  251.                 }  
  252.             }  
  253.         }else{  
  254.             echo "fail";//验证失败  
  255.         }  
  256.     }  
  257.     /** 
  258.      * 将剩下参数进行url_decode, 然后进行字典排序,组成字符串,得到待签名字符串 
  259.      * @param unknown $params 
  260.      * @param unknown $sign 
  261.      * @return boolean 
  262.      */  
  263.     function verifyParameters($params$sign) {  
  264.           
  265.         $public_key_path = './Public/key/alipay_public_key.pem';//公钥(合作伙伴)  
  266.   
  267.         $params = $this->filterSignParameter($params);  
  268.         ksort($params);  
  269.         reset($params);  
  270.       
  271.         $content = urldecode(http_build_query($params));  
  272.          
  273.         return $this->rsaVerify($content$public_key_path$sign);  
  274.   
  275.     }  
  276.     /** 
  277.      * 过滤参数,去除sign/sign_type参数 
  278.      * @param $params 
  279.      * @return <Array> 
  280.      */  
  281.     function filterSignParameter($params) {  
  282.         $result = array();  
  283.         foreach ($params as $key => $value) {  
  284.             if ($key != 'sign' && $key != 'sign_type' && $value) {  
  285.                 $result[$key] = $value;  
  286.             }  
  287.         }  
  288.         return $result;  
  289.     }  
  290.     /** 
  291.      * RSA验签,注意验签的公钥是支付宝的公钥,不是自己生成的rsa公钥,可以在淘宝的demo中获得 
  292.      * @param $data string 待签名数据 
  293.      * @param $ali_public_key_path string 支付宝的公钥文件路径 
  294.      * @param $sign string 要校对的的签名结果 
  295.      * @return <Boolean> 验证结果 
  296.      * @throws Exception 
  297.      */  
  298.     function rsaVerify($data$ali_public_key_path$sign) {  
  299.         $pubKey = file_get_contents($ali_public_key_path);  
  300.         $res = openssl_get_publickey($pubKey);  
  301.         if(!$res){  
  302.             throw new \Exception('公钥格式错误');  
  303.         }  
  304.         $result = (bool)openssl_verify($database64_decode($sign), $res);  
  305.         openssl_free_key($res);  
  306.         return $result;  
  307.     }  
  308.     function verifyFromServer($notify_id) {  
  309.         $transport = "http";  
  310.         $partner = "xxxxxxxxxxx";  
  311.         $cacert = "./Public/key/cacert.pem";  
  312.         $transport = strtolower(trim($transport));  
  313.         $partner = trim($partner);  
  314.         $veryfy_url = "http://notify.alipay.com/trade/notify_query.do?partner=$partner¬ify_id=$notify_id";  
  315.         $curl = curl_init($veryfy_url);  
  316.         curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头  
  317.         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证  
  318.         curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证  
  319.         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);// 显示输出结果  
  320.         curl_setopt($curl, CURLOPT_CAINFO, $cacert);//证书地址  
  321.         $responseText = curl_exec($curl);  
  322.         // var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容  
  323.         curl_close($curl);  
  324.         return $responseText;  
  325.     }  
[php] view plain copy
 
 print?
  1.       /**格式化公钥 
  2.     * $pubKey PKCS#1格式的公钥串 
  3.     * return pem格式公钥, 可以保存为.pem文件 
  4.     */  
  5.    function formatPubKey($pubKey) {  
  6.        $fKey = "-----BEGIN PUBLIC KEY-----\n";  
  7.        $len = strlen($pubKey);  
  8.        for($i = 0; $i < $len; ) {  
  9.            $fKey = $fKey . substr($pubKey$i, 64) . "\n";  
  10.            $i += 64;  
  11.        }  
  12.        $fKey .= "-----END PUBLIC KEY-----";  
  13.        return $fKey;  
  14.    }  
  15. }