Selaa lähdekoodia

支付回调的全过程

lizhen_gitee 3 vuotta sitten
vanhempi
commit
aea7382bc7

+ 42 - 0
application/admin/controller/Payment.php

@@ -0,0 +1,42 @@
+<?php
+namespace app\admin\controller;
+use \think\Db;
+use addons\epay\library\Service;
+
+/*
+ *处理支付结果 接受服务器和服务器之间的通知
+ */
+class Payment extends \think\Controller
+{
+    public function notify(){
+        $paytype = 'wechat';
+
+        $pay = Service::checkNotify($paytype);
+        filePut('[payment][receive]异步回调开始:'.json_encode($pay));
+        if (!$pay) {
+            filePut('[payment][receive]异步回调开始:签名错误');
+            return;
+        }
+        $data = $pay->verify();
+
+        $payamount = $data['total_fee'] / 100;
+        $out_trade_no = $data['out_trade_no'];
+
+        //你可以在此编写订单逻辑
+        $paymentdo = new \app\common\model\Paymentdo;
+        $rs = $paymentdo->receive($out_trade_no,$payamount);
+        if(!$rs){
+            filePut('[payment][receive]异步回调结果:逻辑false');
+            exit;
+        }
+
+        filePut('[payment][receive]异步回调结果:Allsuccess');
+
+        echo $pay->success();
+    }
+
+
+
+
+}
+?>

+ 1 - 1
application/api/controller/Pay.php

@@ -60,7 +60,7 @@ class Pay extends Api
         //创建回调
         $even_data = [];
         $even_data['event'] = 'success';
-        $even_data['class'] = 'app\common\model\Wallet';
+        $even_data['class'] = 'app\common\model\Recharge';
         $even_data['method'] = 'paySucc';
         $even_data['args'] = json_encode(['user_id'=>$uid,'days'=>$recharge_config['days']]);
         $even_data['pay_no'] = $pay_no;

+ 144 - 3
application/common.php

@@ -510,19 +510,33 @@ function one_domain_image($one){
     if(!$one){
         return $one;
     }
-    $domain_name = config('site.domain_name');
+    //$domain_name = config('site.domain_name');
     if(strpos($one,',')){
         //逗号隔开的多个图片
         $one = explode(',',$one);
         foreach($one as $k => $v){
-            $one[$k] = $v ? $domain_name.$v : $v;
+            //$one[$k] = $v ? $domain_name.$v : $v;
+            $one[$k] = localpath_to_netpath($v);
         }
         $one = implode(',',$one);
     }else{
-        $one = $one ? $domain_name.$one : $one;
+        //$one = $one ? $domain_name.$one : $one;
+        $one = localpath_to_netpath($one);
     }
     return $one;
 }
+//本地地址转换为网络地址
+function localpath_to_netpath($path)
+{
+    if (empty($path)) {
+        return '';
+    } elseif (strrpos($path, 'http') !== false) {
+        return $path;
+    } else {
+        return config('site.domain_name') . str_replace("\\", "/", $path);
+        //return config('site.domain_name') . $path;
+    }
+}
 
 //秒 转换 日月分
 function Sec2Time($time){
@@ -578,6 +592,120 @@ function birthtime_to_age($birthtime){
 
     return $age;
 }
+if(!function_exists('mk_dir')) {
+    /**
+     * 新建目录
+     */
+    function mk_dir($dir, $mode = 0770, $tmp = true)
+    {
+        $mode = 0770;
+        if(is_file($dir)) {
+            //有同名文件
+            return false;
+        } else {
+            if(!is_dir($dir)) { //目录不存在
+                $dir_up = dirname($dir); //上级目录
+                if(!is_dir($dir_up)) {
+                    //上级不存在
+                    $rs = @mk_dir($dir_up);
+                    if(!$rs) return false;
+                }
+                $rs = @mkdir($dir, $mode); //新建
+                if(!$rs) return false;
+                $rs = @chmod($dir, $mode); //改权限
+                if(!$rs) return false;
+            }
+            return true;
+        }
+    }
+}
+/**
+ * 在线支付日志
+ */
+function filePut($info,$text='notify.txt'){
+    if(is_array($info)) {
+        $info = json_encode($info, JSON_UNESCAPED_UNICODE);
+    }
+    if(!file_exist(RUNTIME_PATH.'paylog/')) {
+        mk_dir(RUNTIME_PATH.'paylog/');
+    }
+    $file = RUNTIME_PATH.'paylog/'.$text;
+    touch_file($file);
+    file_put_contents($file, "\r\n".date('Y-m-d H:i:s').' '.$info, FILE_APPEND);
+}
+if(!function_exists('touch_file')) {
+    /**
+     * 新建文件
+     */
+    function touch_file($file = '')
+    {
+        if($file) {
+            if(!file_exists($file)) {
+                @touch($file);
+                @chmod($file, 0770);
+            }
+        }
+    }
+}
+if(!function_exists('file_exist')) {
+    /**
+     * 检测文件是否存在
+     * @param $file
+     * @return string
+     */
+    function file_exist($file)
+    {
+        if(false === strpos($file, 'http')) { //本地文件
+
+            if(0 === strpos($file, '/upload')) {
+                $file = '.'.$file;
+            }
+            return file_exists($file);
+        } else { //网络文件
+
+            $ch = curl_init();
+            curl_setopt($ch, CURLOPT_URL, $file);
+            curl_setopt($ch, CURLOPT_TIMEOUT, 2);
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+            curl_exec($ch);
+            $status =  curl_getinfo($ch,CURLINFO_HTTP_CODE);
+            curl_close($ch);
+            if(in_array(substr($status, 0, 1), [2, 3])) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+}
+
+/**
+ * 发起HTTPS请求
+ */
+function curl_post($url, $data, $header = '', $timeOut = 0)
+{
+    //初始化curl
+    $ch = curl_init();
+    //参数设置
+    curl_setopt($ch, CURLOPT_URL, $url);
+    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
+    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+    curl_setopt($ch, CURLOPT_TIMEOUT, $timeOut);
+    curl_setopt($ch, CURLOPT_HEADER, 0);
+    curl_setopt($ch, CURLOPT_POST, 1);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+    if($header != '') {
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
+    }
+    $result = curl_exec($ch);
+    //连接失败
+    if($result == FALSE) {
+        //\think\Log::record('[ CURL ] ERROR ' . curl_error($ch)."\n".var_export(debug_backtrace(), true)."\n", 'error');
+    }
+    curl_close($ch);
+    return $result;
+}
 /**
  * 发起HTTP GET请求
  */
@@ -606,4 +734,17 @@ function curl_get($url)
     } else {
         return false;
     }
+}
+//创建订单号
+function createUniqueNo($prifix = 'P',$id = 0)
+{
+    $s = 0;
+    $ms = 0;
+    list($ms, $s) = explode(' ', microtime());
+
+    $ms = substr($ms, 2, 6); //获取微妙
+
+    $rt = $prifix.date('ymdHis', $s).$ms.$id; //年月日时分秒.用户id对10取余.微秒
+
+    return $rt;
 }

+ 215 - 0
application/common/model/Paymentdo.php

@@ -0,0 +1,215 @@
+<?php
+namespace app\common\model;
+use think\Db;
+use app\common\model\Wallet;
+/*
+ *处理支付结果 接受服务器和服务器之间的通知
+ */
+class Paymentdo
+{
+
+
+    //接受服务器的通知
+    public function receive($orderId,$payamount)
+    {
+
+        filePut('[payment][receive]异步回调开始');
+        //$orderId = Pay::getOrderId();
+        if (!$orderId)
+        {
+            filePut('[payment][receive]支付号未匹配');
+            return false;
+        }
+
+        $where = array();
+        $where['pay_no'] = $orderId;
+        $info = Db::name('pay_order')->where($where)->find();
+        if ($info)
+        {
+            if($info['status'])
+            {
+                filePut('[payment][receive]本支付号已处理过 '.$orderId);
+                return true;
+            }
+            else
+            {
+                filePut('[payment][receive]开始支付 '.$orderId);
+            }
+            //获取到它使用的支付接口
+            //$pay = new Pay($info['payment_class']);
+            //Db::startTrans(); 修改为:充值+扣款,防止扣款失败时充值也回滚
+            //if ($rs = $pay->receive())
+            if (1 === 1)
+            {
+                $this->touchEvent('success', $orderId);
+                filePut('[payment][receive]支付完成,结果 success, '.$orderId);
+                return true;
+            }
+            else
+            {
+                $this->touchEvent('failed', $orderId);
+                filePut('[payment][receive]支付完成,结果 failed, '.$orderId);
+            }
+            //filePut('[payment][receive]支付完成,结果  '.json_encode($rs).', '.$orderId);
+        }
+        else
+        {
+            filePut('[payment][receive]无效支付号 '.$orderId);
+            return false;
+        }
+
+        return false;
+        //1.服务器发回处理结果
+        //2.这里交给核心类处理
+        //3.核心类交给支付接口判断支付成功还是失败
+        //4.根据支付接口的返回结果,核心类调用之前绑定好的事件.
+    }
+
+    /**
+     * 执行绑定事件
+     * @param  string     $event   事件名称
+     * @param  string     $order_id 订单号
+     */
+    public function touchEvent($event, $orderId)
+    {
+        //检查该订单是否已绑定事件
+        $pay_event = Db::name('pay_event');
+        $pay_order = Db::name('pay_order');
+
+        $where = [];
+        $where['pay_no'] = $orderId;
+        $where['event'] = $event;
+        $info = $pay_event->where($where)->find();
+        if(!$info||!isset($info['class']))
+        {return;}
+
+        $class = new $info['class'];
+
+        db()->startTrans();
+        $orderInfo = $pay_order->where(['pay_no' => $orderId])->lock(true)->find();
+        filePut("[PAY][touchEvent] STRAT" . $orderId);
+
+        //如果找到了绑定的事件
+        if ($info) {
+            $args = json_decode($info['args'], true); //强制转换为数组格式
+            $args['moneynumber'] = $orderInfo['money'];//兼容以前的设计,追加money
+            if ($event === 'success') {
+                if($orderInfo['status'] === 0)//成功事件只触发1次
+                {
+                    $pay_order->where(['pay_no' => $orderId])->update(['status'=>2]);
+                    db()->commit();
+                    filePut('[PAY][touchEvent] success start'.$orderId);//.$info['args']
+                    call_user_func_array([$class, $info['method']], [$orderId, $args]);
+                    //得到事件模型类
+                    filePut('[PAY][touchEvent] success end '.$orderId);
+                }
+            }
+            else
+            {
+                call_user_func_array([$class, $info['method']], [$orderId, $args]);
+                filePut('[PAY][touchEvent] '.$event.' fail'.$orderId);
+            }
+        }
+    }
+
+    //接受浏览器的同步通知
+    public function callback()
+    {
+        filePut('[payment][callback]同步回调开始');
+        $orderId = Pay::getOrderId();
+        if (!$orderId)
+        {
+            filePut('[payment][callback]无法分析到支付号未匹配');
+            $this->error('无效支付!');
+        }
+
+        $where = array();
+        $where['pay_no'] = $orderId;
+        $info = Db::name('pay_order')->where($where)->find();
+        if ($info)
+        {
+            //获取到它使用的支付接口
+            $pay = new Pay($info['payment_class']);
+            $rs = $pay->callback();
+            if (!$info['status'])
+            {
+                filePut('[payment][callback]开始支付 '.$orderId);
+                //Db::startTrans(); 修改为:充值+扣款,防止扣款失败时充值也回滚
+                if ($rs)
+                {
+                    $pay->touchEvent('success', $orderId);
+                    $pay->printMessage('success', 'callback结果 success, '.$orderId);
+                    filePut('[payment][callback]支付完成,结果 success, '.$orderId);
+                    filePut('[payment][callback]支付完成,结果  '.json_encode($rs).', '.$orderId);
+                }
+                else
+                {
+                    $pay->touchEvent('failed', $orderId);
+                    $pay->printMessage('failed', 'callback结果 failed, '.$orderId);
+                    filePut('[payment][callback]支付完成,结果 failed, '.$orderId);
+                    filePut('[payment][callback]支付完成,结果  '.json_encode($rs).', '.$orderId);
+                    /*if (IS_MOBILE)
+                    {
+                        $url = session('redirect_fail_uri', '','user')?:'/wap/';
+                        session('redirect_fail_uri', null,'user');
+                        $this->redirect($url);
+                    }
+                    else
+                    {*/
+                        $this->error('支付失败!');
+                    /*}*/
+                }
+            }
+        }
+        else
+        {
+            filePut('[payment][callback]无效支付号 '.$orderId);
+            /*if (IS_MOBILE)
+            {
+                $url = session('redirect_fail_uri', '','user')?:'/wap/';
+                session('redirect_fail_uri', null,'user');
+                $this->redirect($url);
+            }
+            else
+            {*/
+                $this->error('无效支付订单号!');
+           /* }*/
+        }
+
+        //以下代码用于同步支付成功后回跳,请定制
+        /*$pays = Pay::getInstalled();
+        $pinfo = $pays[Pay::$payname];*/
+        $pinfo = ['type'=>'pc'];
+        filePut('[payment][callback]回调地址 '.json_encode(session('redirect_uri','','user')));
+        //同步通知处理完进行回跳转
+        if ('app'==$pinfo['type']||request()->isAjax())
+        {
+            //app同步请求
+            if($rs)
+                $pay->printMessage('success', 'app callback结果 success, '.$orderId);
+            else
+                $pay->printMessage('failed', 'app callback结果 failed, '.$orderId);
+        }
+        else
+        {
+            if (session('redirect_uri','','user'))
+            {
+                $url = str_replace('.html', '', session('redirect_uri','','user'));
+                session('redirect_uri', null, 'user');
+                $this->redirect($url);
+            }
+            elseif('wap'==$pinfo['type'])
+                $this->redirect('/wap/');
+            else
+                $this->redirect('/index/user/walletcenter');
+        }
+        exit();
+        //1.服务器发回处理结果
+        //2.这里交给核心类处理
+        //3.核心类交给支付接口判断支付成功还是失败
+        //4.根据支付接口的返回结果,核心类调用之前绑定好的事件.
+    }
+
+
+}
+?>

+ 68 - 0
application/common/model/Recharge.php

@@ -0,0 +1,68 @@
+<?php
+namespace app\common\model;
+
+use \think\Db;
+//use \com\Pay\Pay;
+/**
+ * 订单支付模型
+ */
+class Recharge
+{
+
+
+    /**
+     * 支付回调
+     */
+    public function paySucc($orderId,$args){
+        $order = Db::name('pay_order')->where(array('pay_no'=>$orderId,'status'=>2))->find();
+
+        if( $order )
+        {
+
+            Db::startTrans();
+            //更新订单状态
+            $rs = Db::name('pay_order')->where(['id'=>$order['id']])->update(['status'=>1]);
+            if(!$rs){
+                Db::rollback();
+                filePut('[wallet][paySucc] pay_order update fail'.$orderId);
+                abort(500,lang('订单更新失败'));
+            }
+
+            //先充值
+            $user_info = Db::name('wallet')->where('user_id',$args['user_id'])->lock(true)->find();
+            if($user_info['vip_endtime'] < time()){
+                //过期了
+                $vip_endtime = time() + (intval($args['days']) * 86400);
+            }else{
+                //追加vip
+                $vip_endtime = $user_info['vip_endtime'] + (intval($args['days']) * 86400);
+            }
+            $result = Db::name('wallet')->where('user_id',$args['user_id'])->update(['vip_endtime'=>$vip_endtime]);
+            if($result === false)
+            {
+                Db::rollback();
+                filePut('[wallet][paySucc]网银充值入账更新vip时间失败 recharge money fail'.$orderId.$result['msg']);
+                abort(500,lang('网银充值入账 model wallet recharge money fail'));
+            }
+
+            Db::commit();
+
+            //再扣款
+            /* Db::startTrans();
+             //需要更新某个支付表
+             if(isset($args['payids']) && $args['payids'])
+             {
+                 logic('Cashier')->paySuccess($args,true);
+                 filePut('[wallet][paySucc] end success '.$orderId);
+            }
+            Db::commit();*/
+
+
+            return true;
+        }else{
+            filePut('[wallet][paySucc]在线支付订单有误 '.$orderId);
+            abort(500,lang('model wallet fail'));
+            return false;
+        }
+    }
+}

+ 57 - 0
public/notify.php

@@ -0,0 +1,57 @@
+<?php
+//记录支付回调数据
+ignore_user_abort(); // run script in background
+set_time_limit(30);
+// 日志文件 start
+$log_base_dir = '../runtime/paylog/';
+if (!is_dir($log_base_dir))
+{
+    mkdir($log_base_dir, 0770, true);
+    @chmod($log_base_dir, 0770);
+}
+$notify_file = $log_base_dir.'notify.txt';
+if(!file_exists($notify_file)) {
+    @touch($notify_file);
+    @chmod($notify_file, 0770);
+}
+if(filesize($notify_file)>5242880)//大于5M自动切换
+{
+    rename($notify_file, $log_base_dir.'notify_'.date('Y_m_d_H_i_s').'.txt');
+}
+if(!file_exists($notify_file)) {
+    @touch($notify_file);
+    @chmod($notify_file, 0770);
+}
+// 日志文件 end
+$_REQUEST = isset($_REQUEST) ? $_REQUEST : array();
+if($_REQUEST) {
+    file_put_contents($notify_file, "\r\n\r\n".date('Y-m-d H:i:s')." [notify][入口接收request]".json_encode($_REQUEST), FILE_APPEND);
+} else {
+    $xml = file_get_contents("php://input");
+    file_put_contents($notify_file, "\r\n\r\n".date('Y-m-d H:i:s')." [notify][入口接收php://input流原始数据] \n".$xml, FILE_APPEND);
+    $xmlObj = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
+    file_put_contents($notify_file, "\r\n\r\n".date('Y-m-d H:i:s')." [notify][入口接收php://input流] ".json_encode($xmlObj), FILE_APPEND);
+}
+
+ini_set('display_errors','On');
+
+//$_GET['s']='/Admin/Payment/receive/';
+
+// 定义应用目录
+define('APP_PATH', __DIR__ . '/../application/');
+
+// 加载框架引导文件
+require __DIR__ . '/../thinkphp/base.php';
+
+// 绑定到admin模块
+\think\Route::bind('admin/Payment/notify');
+
+// 关闭路由
+\think\App::route(false);
+
+// 设置根url
+\think\Url::root('');
+
+// 执行应用
+\think\App::run()->send();
+?>