From 8c91a1e79404aea818df7d367278de76be1d42e9 Mon Sep 17 00:00:00 2001 From: 15090180611 <215509543@qq.com> Date: Fri, 8 Nov 2024 18:18:06 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E8=AE=A2=E5=8D=95=E6=AD=A3?= =?UTF-8?q?=E5=90=91=E6=B5=81=E7=A8=8B=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/school/HourOrder.php | 123 ++++ application/api/controller/school/Order.php | 80 ++- application/api/controller/school/Pay.php | 245 ++++++++ application/common.php | 63 +++ .../listener/classesorder/OrderHook.php | 34 ++ application/common/model/BaseModel.php | 3 + application/common/model/User.php | 82 ++- .../model/school/classes/ClassesLib.php | 6 +- .../model/school/classes/order/Order.php | 535 +++++++++++++++++- .../school/classes/order/OrderDetail.php | 12 +- .../model/school/classes/order/OrderLog.php | 45 ++ 11 files changed, 1205 insertions(+), 23 deletions(-) create mode 100644 application/api/controller/school/HourOrder.php create mode 100644 application/api/controller/school/Pay.php create mode 100644 application/common/listener/classesorder/OrderHook.php diff --git a/application/api/controller/school/HourOrder.php b/application/api/controller/school/HourOrder.php new file mode 100644 index 0000000..6d88678 --- /dev/null +++ b/application/api/controller/school/HourOrder.php @@ -0,0 +1,123 @@ +model = new OrderModel; + parent::_initialize(); + + //判断登录用户是否是员工 + } + + + /** + * @ApiTitle( 订单详情) + * @ApiSummary(订单详情) + * @ApiRoute(/api/school/order/detail) + * @ApiMethod(GET) + * @ApiParams(name = "id", type = "int",required=true,description = "订单id或订单号") + * @ApiReturn({ + * + *}) + */ + public function detail(){ + $id = $this->request->get('id/d',''); + + if(empty($id)){ + $this->error(__('缺少必要参数')); + } + + try { + $res = OrderModel::getDetail($id); + } catch (\Exception $e){ +// Log::log($e->getMessage()); + $this->error($e->getMessage(),['errcode'=>$e->getCode()]); + } + $this->success('获取成功', ['detail' => $res]); + } + + + + + + /** + * @ApiTitle( 订单确认/订单计算接口) + * @ApiSummary(订单确认/订单计算接口【有过期时间】) + * @ApiRoute(/api/school/order/confirm) + * @ApiMethod(POST) + * @ApiParams(name = "classes_lib_id", type = "int",required=true,description = "订单id或订单号") + * @ApiParams(name = "order_no", type = "string",required=false,description = "缓存key") + * @ApiParams(name = "is_compute", type = "int",required=false,description = "是否重新计算并更新缓存 默认传1") + * @ApiReturn({ + * + *}) + */ + public function confirm(){ + $user_id = 0; + $user = $this->auth->getUser();//登录用户 + if($user)$user_id = $user['id']; + $classes_lib_id = $this->request->post('classes_lib_id/d', 0); //课程id +// $param = urldecode($this->request->post('param/s', "{}")); //参数 + $param = []; //参数 + $order_no = $this->request->post('order_no/s', ''); //订单号 + $is_compute = $this->request->post('is_compute/d', 1); //是否重新计算并更新缓存 + try{ + //当前申请状态 + $res = $this->model->confirm($user_id, $classes_lib_id,$order_no,$param, $is_compute); + }catch (\Exception $e){ +// Log::log($e->getMessage()); + $this->error($e->getMessage(),['errcode'=>$e->getCode()]); + } + $this->success('执行成功,可用缓存key下单', $res); + } + + + /** + * @ApiTitle( 订单下单接口) + * @ApiSummary(订单下单接口) + * @ApiRoute(/api/school/order/create) + * @ApiMethod(POST) + * @ApiParams(name = "order_no", type = "string",required=true,description = "缓存key") + * @ApiReturn({ + * + *}) + */ + public function create(){ + $user_id = 0; + $user = $this->auth->getUser();//登录用户 + if($user)$user_id = $user['id']; + $order_no = $this->request->post('order_no/s', ''); //订单号 + $remark = $this->request->post('remark/s', ''); //下单备注 +// repeat_filter("appointment\order\create".$user_id, 2); + try{ + //当前申请状态 + $res = $this->model->cacheCreateOrder($order_no,$user_id,$remark,true); + }catch (\Throwable $e){ +// file_put_contents("test.txt",$e->getMessage().$e->getFile().$e->getLine());//写入文件,一般做正式环境测试 +// Log::log($e->getMessage()); + $this->error($e->getMessage(),['errcode'=>$e->getCode()]);; + } + $this->success('订单创建成功,缓存key被消耗', $res); + } + +} + diff --git a/application/api/controller/school/Order.php b/application/api/controller/school/Order.php index 8669a35..b6f84cd 100644 --- a/application/api/controller/school/Order.php +++ b/application/api/controller/school/Order.php @@ -2,6 +2,7 @@ namespace app\api\controller\school; +use app\common\model\school\classes\order\Order as OrderModel; use app\common\model\school\classes\Teacher as Teachermodel; /** @@ -9,7 +10,7 @@ use app\common\model\school\classes\Teacher as Teachermodel; */ class Order extends Base { - protected $noNeedLogin = ["detail",'people','spec']; + protected $noNeedLogin = []; protected $noNeedRight = '*'; protected $model = null; @@ -21,7 +22,7 @@ class Order extends Base protected function _initialize() { - $this->model = new Teachermodel; + $this->model = new OrderModel; parent::_initialize(); //判断登录用户是否是员工 @@ -29,11 +30,11 @@ class Order extends Base /** - * @ApiTitle( x详情) - * @ApiSummary(xx详情) - * @ApiRoute(/api/school/teacher/detail) + * @ApiTitle( 订单详情) + * @ApiSummary(订单详情) + * @ApiRoute(/api/school/order/detail) * @ApiMethod(GET) - * @ApiParams(name = "id", type = "int",required=true,description = "老师id") + * @ApiParams(name = "id", type = "int",required=true,description = "订单id或订单号") * @ApiReturn({ * *}) @@ -46,12 +47,77 @@ class Order extends Base } try { - $res = $this->model->detail($id); + $res = OrderModel::getDetail($id); } catch (\Exception $e){ // Log::log($e->getMessage()); $this->error($e->getMessage(),['errcode'=>$e->getCode()]); } $this->success('获取成功', ['detail' => $res]); } + + + + + + /** + * @ApiTitle( 订单确认/订单计算接口) + * @ApiSummary(订单确认/订单计算接口【有过期时间】) + * @ApiRoute(/api/school/order/confirm) + * @ApiMethod(POST) + * @ApiParams(name = "classes_lib_id", type = "int",required=true,description = "订单id或订单号") + * @ApiParams(name = "order_no", type = "string",required=false,description = "缓存key") + * @ApiParams(name = "is_compute", type = "int",required=false,description = "是否重新计算并更新缓存 默认传1") + * @ApiReturn({ + * + *}) + */ + public function confirm(){ + $user_id = 0; + $user = $this->auth->getUser();//登录用户 + if($user)$user_id = $user['id']; + $classes_lib_id = $this->request->post('classes_lib_id/d', 0); //课程id +// $param = urldecode($this->request->post('param/s', "{}")); //参数 + $param = []; //参数 + $order_no = $this->request->post('order_no/s', ''); //订单号 + $is_compute = $this->request->post('is_compute/d', 1); //是否重新计算并更新缓存 + try{ + //当前申请状态 + $res = $this->model->confirm($user_id, $classes_lib_id,$order_no,$param, $is_compute); + }catch (\Exception $e){ +// Log::log($e->getMessage()); + $this->error($e->getMessage(),['errcode'=>$e->getCode()]); + } + $this->success('执行成功,可用缓存key下单', $res); + } + + + /** + * @ApiTitle( 订单下单接口) + * @ApiSummary(订单下单接口) + * @ApiRoute(/api/school/order/create) + * @ApiMethod(POST) + * @ApiParams(name = "order_no", type = "string",required=true,description = "缓存key") + * @ApiReturn({ + * + *}) + */ + public function create(){ + $user_id = 0; + $user = $this->auth->getUser();//登录用户 + if($user)$user_id = $user['id']; + $order_no = $this->request->post('order_no/s', ''); //订单号 + $remark = $this->request->post('remark/s', ''); //下单备注 +// repeat_filter("appointment\order\create".$user_id, 2); + try{ + //当前申请状态 + $res = $this->model->cacheCreateOrder($order_no,$user_id,$remark,true); + }catch (\Throwable $e){ +// file_put_contents("test.txt",$e->getMessage().$e->getFile().$e->getLine());//写入文件,一般做正式环境测试 +// Log::log($e->getMessage()); + $this->error($e->getMessage(),['errcode'=>$e->getCode()]);; + } + $this->success('订单创建成功,缓存key被消耗', $res); + } + } diff --git a/application/api/controller/school/Pay.php b/application/api/controller/school/Pay.php new file mode 100644 index 0000000..7dc19a2 --- /dev/null +++ b/application/api/controller/school/Pay.php @@ -0,0 +1,245 @@ +model = new OrderModel; + parent::_initialize(); + + //判断登录用户是否是员工 + } + + + /** + * @ApiTitle( 课程第三方支付) + * @ApiSummary(课程第三方支付(微信等支付)) + * @ApiRoute(/api/school/pay/payment) + * @ApiMethod(POST) + * @ApiParams(name = "order_no", type = "string",required=true,description = "订单id或订单号") + * @ApiParams(name = "type", type = "string",required=true,description = "服务商:alipay=支付宝,wechat=微信") + * @ApiParams(name = "platform", type = "string",required=true,description = "平台:web=PC网页支付,wap=H5手机网页支付,app=APP支付,scan=扫码支付,mp=微信公众号支付,miniapp=微信小程序支付") + * @ApiParams(name = "openid", type = "string",required=false,description = "用户openid(非必填)") + * @ApiReturn({ + * + *}) + */ + public function payment() + { + $order_no = $this->request->post('order_no/s'); + $type = $this->request->post('type/s'); + $method = $this->request->post('platform/s'); + $openid = $this->request->post('openid/s', ""); + $platform = $method; + try { + //订单支付前校验 + $this->model->checkPay($order_no,$type); + $order = OrderModel::where('order_no', $order_no)->find(); + $amount = $order["totalprice"]; + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + + + + + + if (!$amount || $amount < 0) { + $this->error("支付金额必须大于0"); + } + + if (!$type || !in_array($type, ['alipay', 'wechat'])) { + $this->error("支付类型不能为空"); + } + + if (in_array($method, ['miniapp', 'mp']) && !$openid) { + + // 微信公众号,小程序支付,必须有 openid + if (isset($openid) && $openid) { + // 如果传的有 openid + } else { + // 没有 openid 默认拿下单人的 openid + $oauth = Third::where([ + 'user_id' => $order->user_id, + 'platform' => $type, + 'apptype' => $platform + ])->find(); + + $openid = $oauth ? $oauth->openid : ''; + } + + if (!$openid) { + // 缺少 openid + $this->error("授权过期,请您重新授权登录"); + } + } + + //订单号 + $out_trade_no = $order_no; + + //订单标题 + $title = '课程订单['.$out_trade_no."]支付"; + + //回调链接 + $notifyurl = $this->request->root(true) . '/api/school/pay/notifyx/paytype/' . $type. '/platform/' . $method; + $returnurl = $this->request->root(true) . '/api/school/pay/returnx/paytype/' . $type . '/out_trade_no/' . $out_trade_no; + + $response = Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method, $openid); + + return $response; + } + + /** + * @ApiTitle( 第三方支付支付成功回调) + * @ApiSummary(第三方支付成功回调) + * @ApiRoute(/api/school/pay/notifyx) + * @ApiMethod(GET) + * @ApiReturn({ + * + *}) + */ + public function notifyx() + { + $paytype = $this->request->param('paytype'); + $platform = $this->request->param('platform'); + $pay = Service::checkNotify($paytype); + if (!$pay) { + return json(['code' => 'FAIL', 'message' => '失败'], 500, ['Content-Type' => 'application/json']); + } + $payment = $paytype; + // 获取回调数据,V3和V2的回调接收不同 + $data = Service::isVersionV3() ? $pay->callback() : $pay->verify(); + + + + Log::write('notifyx-result:'. json_encode($data)); + + $out_trade_no = $data['out_trade_no']; + $out_refund_no = $data['out_biz_no'] ?? ''; + + + + // 判断是否是支付宝退款(支付宝退款成功会通知该接口) + if ($paytype == 'alipay' // 支付宝支付 + && $data['notify_type'] == 'trade_status_sync' // 同步交易状态 + && $data['trade_status'] == 'TRADE_CLOSED' // 交易关闭 + && $out_refund_no // 退款单号 + ) { + // 退款回调 + +// $this->refundFinish($out_trade_no, $out_refund_no,$platform); + + return $pay->success()->send(); + } + + + + + + + // 判断支付宝微信是否是支付成功状态,如果不是,直接返回响应 + if ($payment == 'alipay' && $data['trade_status'] != 'TRADE_SUCCESS') { + // 不是交易成功的通知,直接返回成功 + return $pay->success()->send(); + } + if ($payment == 'wechat' && ($data['result_code'] != 'SUCCESS' || $data['return_code'] != 'SUCCESS')) { + // 微信交易未成功,返回 false,让微信再次通知 + return false; + } + + + try { + //微信支付V3返回和V2不同 + if (Service::isVersionV3() && $paytype === 'wechat') { + $data = $data['resource']['ciphertext']; + $data['total_fee'] = $data['amount']['total']; + } + + \think\Log::record($data); + + $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100; + $out_trade_no = $data['out_trade_no']; + + \think\Log::record("回调成功,订单号:{$out_trade_no},金额:{$payamount}"); + + //你可以在此编写订单逻辑 + + // 支付成功流程 + //获取支付金额、订单号 + $pay_fee = $payment == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100; + + //你可以在此编写订单逻辑 + $order = OrderModel::getNopayOrder($out_trade_no); + + if (!$order || $order["status"] != '0') { + // 订单不存在,或者订单已支付 + return $pay->success()->send(); + } + $notify = [ + 'order_no' => $data['out_trade_no'], + 'transaction_id' => $payment == 'alipay' ? $data['trade_no'] : $data['transaction_id'], + 'notify_time' => date('Y-m-d H:i:s', strtotime($data['time_end'])), + 'buyer_email' => $payment == 'alipay' ? $data['buyer_logon_id'] : $data['openid'], + 'payment_json' => json_encode($data->all()), + 'pay_fee' => $pay_fee, + 'pay_type' => $payment, // 支付方式 + 'platform' => $platform, + ]; + + $this->model->paySuccess($order['order_sn'],$notify,$pay_fee,true,true); + + + + } catch (Exception $e) { + \think\Log::record("回调逻辑处理错误:" . $e->getMessage(), "error"); + } + + //下面这句必须要执行,且在此之前不能有任何输出 + if (Service::isVersionV3()) { + return $pay->success()->getBody()->getContents(); + } else { + return $pay->success()->send(); + } + } + + /** + * 支付返回,网页支付版本(小程序忽略) + */ + public function returnx() + { + $paytype = $this->request->param('paytype'); + $out_trade_no = $this->request->param('out_trade_no'); + $pay = Service::checkReturn($paytype); + if (!$pay) { + $this->error('签名错误', ''); + } + + //你可以在这里定义你的提示信息,但切记不可在此编写逻辑 + $this->success("请返回网站查看支付结果", addon_url("epay/index/index")); + } + + + +} \ No newline at end of file diff --git a/application/common.php b/application/common.php index 3dc14f0..7a3c4a8 100644 --- a/application/common.php +++ b/application/common.php @@ -507,3 +507,66 @@ EOT; return $icon; } } + + + + +/** +获取订单号 + */ +if (!function_exists('get_order_sn')) { + function get_order_sn() + { + $time = explode(' ', microtime()); + $time = $time[1] . $time[0] * 1000; + $time = explode('.', $time); + $time = isset($time[1]) ? $time[1] : 0; + $time = date('YmdHis') + $time; + mt_srand((double) microtime() * 1000000); + return $time . str_pad(mt_rand(1, 99999), 5, '0', 0); + } +} + + + +if (!function_exists('en_code')) { + /** + * 针对于ID的可逆加密函数,也可以用作于邀请码之类 + * @param int $code + */ + function en_code($code) + { + static $source_string = 'E5FCDG3HQA4B1NOPIJ2RSTUV67MWX89KLYZ'; + $num = $code; + $code = ''; + while ($num > 0) { + $mod = $num % 35; + $num = ($num - $mod) / 35; + $code = $source_string[$mod] . $code; + } + if (empty($code[3])) { + $code = str_pad($code, 4, '0', STR_PAD_LEFT); + } + return $code; + } +} +if (!function_exists('de_code')) { + /** + * 针对于ID的可逆加密函数,也可以用作于邀请码之类 + * @param string $code + */ + function de_code($code) + { + static $source_string = 'E5FCDG3HQA4B1NOPIJ2RSTUV67MWX89KLYZ'; + if (strrpos($code, '0') !== false) { + $code = substr($code, strrpos($code, '0') + 1); + } + $len = strlen($code); + $code = strrev($code); + $num = 0; + for ($i = 0; $i < $len; $i++) { + $num += strpos($source_string, $code[$i]) * pow(35, $i); + } + return $num; + } +} diff --git a/application/common/listener/classesorder/OrderHook.php b/application/common/listener/classesorder/OrderHook.php new file mode 100644 index 0000000..0abda58 --- /dev/null +++ b/application/common/listener/classesorder/OrderHook.php @@ -0,0 +1,34 @@ +save(['money' => $after]); //写入日志 - MoneyLog::create(['user_id' => $user_id, 'money' => $money, 'before' => $before, 'after' => $after, 'memo' => $memo]); + MoneyLog::create(['user_id' => $user_id, 'money' => $money, 'before' => $before, 'after' => $after, 'memo' => $memo, 'ext' => $ext?:"","type" =>$type]); } Db::commit(); } catch (\Exception $e) { @@ -114,7 +117,7 @@ class User extends Model * @param int $user_id 会员ID * @param string $memo 备注 */ - public static function score($score, $user_id, $memo) + public static function score($score, $user_id, $memo,$type='',$ext = []) { Db::startTrans(); try { @@ -126,7 +129,7 @@ class User extends Model //更新会员信息 $user->save(['score' => $after, 'level' => $level]); //写入日志 - ScoreLog::create(['user_id' => $user_id, 'score' => $score, 'before' => $before, 'after' => $after, 'memo' => $memo]); + ScoreLog::create(['user_id' => $user_id, 'score' => $score, 'before' => $before, 'after' => $after, 'memo' => $memo, 'ext' => $ext?:"","type" =>$type]); } Db::commit(); } catch (\Exception $e) { @@ -179,4 +182,75 @@ class User extends Model } + + + /**检测用户设置密码情况 + * @param $user_id + * @param $password + * @param bool $check + */ + public static function checkPayPassword($user_id,$password=null,$check=true){ + $user = self::where('id',$user_id)->find(); + if(!$user) throw new \Exception("用户不存在!"); +// if(!$user["mobile"]) throw new \Exception("您未绑定手机号,请您先去绑定手机号!"); + $mobile = $user["mobile"]; + $auth = Auth::instance(); + //是否设置支付密码 + $is_setting = false; + //密码是否正确 + $is_right = false; + //检测是否设置支付密码 + if($user['pay_password'])$is_setting = true; + if($check){ + //校验密码是否正确 + $pay_password = $auth->getEncryptPassword($password, $user['pay_salt']); + if($user['pay_password']==$pay_password)$is_right = true; + } + return compact('is_setting','is_right','mobile'); + } + + + + /** 设置用户支付密码 + * @param $user_id + * @param $code + * @param $password + * @param bool $trans + * @return bool + * @throws \Exception + */ + public static function setPayPassword($user_id, $code,$password,$trans=false,$check_sms=true){ + $user = self::where('id',$user_id)->find(); + if(!$user) throw new \Exception("用户不存在!"); + if($check_sms){ + $ret = Sms::check($user['mobile'], $code, 'pay_password'); + if (!$ret)throw new \Exception("验证码错误"); + } + if(!is_numeric($password) || strlen($password)!=6)throw new \Exception("支付密碼只能設置六位數字!"); + //判断逻辑 + if($trans){ + self::beginTrans(); + } + $res = true; + try{ + $auth = Auth::instance(); + //事务逻辑 + $user['pay_salt'] = Random::alnum(); + $user['pay_password'] = $auth->getEncryptPassword($password, $user['pay_salt']); + $user->save(); + //清除验证码 + if($check_sms)Sms::flush($user['mobile'], 'pay_password'); + if($trans){ + self::commitTrans(); + } + }catch (\Exception $e){ + if($trans){ + self::rollbackTrans(); + } + throw new \Exception($e->getMessage()); + } + return $res; + } + + } diff --git a/application/common/model/school/classes/ClassesLib.php b/application/common/model/school/classes/ClassesLib.php index 0f12520..e65c4b1 100644 --- a/application/common/model/school/classes/ClassesLib.php +++ b/application/common/model/school/classes/ClassesLib.php @@ -243,9 +243,9 @@ class ClassesLib extends Model public function detail($id){ $self = $this->get($id,['shop','teacher']); //下架判断 - if($self['status'] != '1'){ - $this->error("该课程已下架"); - } +// if($self['status'] != '1'){ +// $this->error("该课程已下架"); +// } //参与人数 = 虚拟人数 + 平台人数 return $self; diff --git a/application/common/model/school/classes/order/Order.php b/application/common/model/school/classes/order/Order.php index 7c285a3..04b8e11 100644 --- a/application/common/model/school/classes/order/Order.php +++ b/application/common/model/school/classes/order/Order.php @@ -2,15 +2,23 @@ namespace app\common\model\school\classes\order; + +use app\common\model\BaseModel; +use app\common\model\dyqc\ManystoreShop; +use app\common\model\school\classes\ClassesLib; +use app\common\model\User; +use bw\Common; +use fast\Random; +use think\Cache; use think\Model; use traits\model\SoftDelete; -class Order extends Model +class Order extends BaseModel { use SoftDelete; - + // 表名 protected $name = 'school_classes_order'; @@ -160,25 +168,25 @@ class Order extends Model public function user() { - return $this->belongsTo('app\admin\model\User', 'user_id', 'id', [], 'LEFT')->setEagerlyType(0); + return $this->belongsTo('app\common\model\User', 'user_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function shop() { - return $this->belongsTo('app\admin\model\manystore\Shop', 'shop_id', 'id', [], 'LEFT')->setEagerlyType(0); + return $this->belongsTo(ManystoreShop::class, 'shop_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function lib() { - return $this->belongsTo('app\admin\model\school\classes\Lib', 'classes_lib_id', 'id', [], 'LEFT')->setEagerlyType(0); + return $this->belongsTo(ClassesLib::class, 'classes_lib_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function detail() { - return $this->belongsTo('Detail', 'classes_order_detail_id', 'id', [], 'LEFT')->setEagerlyType(0); + return $this->belongsTo(OrderDetail::class, 'classes_order_detail_id', 'id', [], 'LEFT')->setEagerlyType(0); } @@ -186,4 +194,519 @@ class Order extends Model { return $this->belongsTo('app\admin\model\Admin', 'admin_id', 'id', [], 'LEFT')->setEagerlyType(0); } + + public function getCodeimageAttr($value, $data) + { + if (!empty($value)) return cdnurl($value, true); + } + + public function getCodeoneimageAttr($value, $data) + { + if (!empty($value)) return cdnurl($value, true); + } + + + + /** + * 设置订单缓存 + * @param $uid + * @param $data + * @return bool + */ + public static function setOrderCache($uid, $data) + { + //缓存名 = uid + order_no + $cacheNmae = 'classes_order_cache' . $uid . $data['order_no']; + // 缓存在3600秒之后过期 + return Cache::set($cacheNmae, $data, config("site.unpaid_order_expire_time")); + } + + /** + * 得到订单缓存 + * @param $uid + * @param $order_no + * @return mixed + */ + public static function getOrderCache($uid, $order_no) + { + //缓存名 = uid + order_no + $cacheNmae = 'classes_order_cache' . $uid . $order_no; + // 缓存在3600秒之后过期 + return Cache::get($cacheNmae); + } + + + /** + * 删除订单缓存 + * @param $uid + * @param $order_no + * @return mixed + */ + public static function deleteOrderCache($uid, $order_no) + { + //缓存名 = uid + order_no + $cacheNmae = 'classes_order_cache' . $uid . $order_no; + // 缓存在3600秒之后过期 + return Cache::rm($cacheNmae); + } + + + /**展示订单信息 + * @param $order_no + * @param $price_info + * @return array + */ + public static function showInfo($order_no, $price_info = []){ + $data = []; + $data['order_no'] =$order_no; + $data['order_info'] = self::getDetail($order_no); + return array_merge($data,$price_info); + } + + + + + /**得到订单详情 + * @param $order_no + */ + public static function getDetail($order_no){ + $data = self::where('order_no|id|pay_no',$order_no)->find(); + if(!$data) return $data; + //加载订单详情 + $data->detail->teacher; + //订单用户 + $data->user; + //订单机构 + $data->shop; +// //得到二维码 +// $data->code_url = Common::getQrcode([ +// 'text' => $data['code'], +// 'size' => 200, +// ]); +// $data->one_code_url = Common::getBarcode([ +// 'text' => $data['code'], +// 'size' => 200, +// ]); + + return $data; + } + + + + + + + + /** 订单确认(订单计算) + * @param $user_id 下单用户 + * @param $order_no 订单号(缓存标识) + * @param $classes_lib_id 課程id + * @param $param 額外参数(扩展用) + * @param bool $is_compute 是否重新计算订单 + * @return array + */ + public function confirm($user_id, $classes_lib_id,$order_no,$param=[], $is_compute = false) + { + if ($order_no && !$is_compute) { + //得到缓存 + $data = self::getOrderCache($user_id, $order_no); + if (!$data) throw new \Exception('請您完善預約信息!'); + $price_info = $data['price_info']; + } else { + //订单信息计算 + if(!$param) throw new \Exception('缺少必要信息'); + $this->orderVaild($user_id,$classes_lib_id,$order_no, $param); + //订单支付信息 + $price_info = $this->getCost($user_id,$classes_lib_id,$param); + //生成订单号 + if (!$order_no) $order_no = get_order_sn(); + //生成缓存 + $data = compact('user_id', 'classes_lib_id','param', 'order_no', 'price_info'); + self::setOrderCache($user_id, $data); + } + + \think\Hook::listen('classes_order_create_before', $data); + + //下单数据展示 + return $this->showInfo($order_no, $price_info); + } + + + + + /** + * 根据缓存创建订单 + */ + public function cacheCreateOrder($order_no, $user_id,$remark="", $trans = false) + { + //得到缓存 + $orderInfo = self::getOrderCache($user_id, $order_no); //得到下单信息 + if (!$orderInfo) throw new \Exception('請您完善預約信息!'); + + if ($trans) { + self::beginTrans(); + } + try { + //1订单执行创建 + $this->createOrder($user_id,$orderInfo['classes_lib_id'],$order_no,$orderInfo['param'],$remark); + //5删除缓存 + self::deleteOrderCache($user_id, $order_no); + if ($trans) { + self::commitTrans(); + } + } catch (\Exception $e) { + if ($trans) { + self::rollbackTrans(); + } + throw new \Exception($e->getMessage()); + } + return self::showInfo($order_no); + } + + + + + public function createOrder($user_id,$classes_lib_id,$order_no,$param,$remark='',$other_params=[]){ + + $this->orderVaild($user_id,$classes_lib_id, $order_no, $param,true); + //订单支付信息 + $order_info = self::getCost($user_id,$classes_lib_id,$param,$other_params,true); + + //组装订单数据 + $order_data = $order_info['order_data']; + $order_data["order_no"] = $order_no; + + $res1 = self::create($order_data); + if (!$res1) throw new \Exception('创建订单失败'); + + //課程详情 + $classes_lib_info = $order_info['classes_lib_info']; + + $order_detail_data = []; + $order_detail_data = array_merge($order_detail_data,$classes_lib_info); + $order_detail_data["classes_lib_id"] = $classes_lib_info['id']; + $order_detail_data["classes_order_id"] = $res1['id']; + + unset($order_detail_data['id']); + unset($order_detail_data['createtime']);; + + $orderDetail = (new OrderDetail()); + $orderDetail->allowField(true)->save($order_detail_data); + //更新订单详情id + $res1->classes_order_detail_id = $orderDetail->id; + $res1->save(); + + //记录订单日志 + OrderLog::log($res1['id'],"课程订单创建成功,等待【学员】支付",'user',$user_id); + //7事件 + $data = ['order' => $res1]; + \think\Hook::listen('classes_order_create_after', $data); + return $res1; + } + + + + + + + /**订单校验 + * @param $user_id 用户id + * @param $classes_lib_id 课程id + * @param $order_no 订单号 + * @param $param 表单扩展参数 + * @return bool + */ + public function orderVaild($user_id,$classes_lib_id, $order_no, $param,$check=false){ + if(!$user_id ||!$param )throw new \Exception("缺少必要参数"); + + //默认校验订单是否已创建 + if($check){ + //判断订单是否已创建 + $order_info = self::where(['order_no'=>$order_no]); + if($order_info) throw new \Exception("订单已生成,如需重新下单请退出页面重新进入!"); + } + //校验订单参数 + //课程是否存在并上架 + $classes_lib_info = ClassesLib::where('classes_lib_id',$classes_lib_id); + if(!$classes_lib_info || $classes_lib_info['status']!='1') throw new \Exception("该课程不存在或已下架!"); + + //用户存不存在 + $user_info = User::where('user_id',$user_id)->find(); + if(!$user_info) throw new \Exception("用户不存在!"); + + return true; + } + + + //计算订单所需返回数据接口 + public static function getCost($user_id,$classes_lib_id,$param=[],$other_params=[],$check = false){ + //用户 + $user_info = User::get($user_id); + $user_data = [ + "nickname"=>$user_info["nickname"], + "realname"=>$user_info["realname"], + "avatar"=>$user_info["avatar"], + "mobile"=>$user_info["mobile"], + "money" =>$user_info["money"], + "score" =>$user_info["score"], + ]; + //课程 + $classes_lib_info = ClassesLib::get($classes_lib_id); + $classes_lib_info = $classes_lib_info->toArray(); + + $classes_lib_info['use_num'] = 0;//已使用课时 + $classes_lib_info['sub_num'] = $classes_lib_info['classes_num'];//剩余课时 + //单价 = 课程价格/总课时 + $classes_lib_info["unit_price"] = bcdiv($classes_lib_info['price'],$classes_lib_info['classes_num'],2); + $classes_lib_info["used_price"] = 0; + + + //组装订单下单数据 + $order_data = []; + $order_data["user_id"] = $user_id; + $order_data["manystore_id"] = $classes_lib_info["manystore_id"]; + $order_data["shop_id"] = $classes_lib_info["shop_id"]; + $order_data["classes_lib_id"] = $classes_lib_id; + $order_data["beforeprice"] = $classes_lib_info["price"]; + $order_data["totalprice"] = $classes_lib_info["price"]; + $order_data["payprice"] = $classes_lib_info["price"]; + $order_data["status"] = '0'; + + + return compact('order_data','classes_lib_info','user_data'); + } + + + + /**得到待支付订单 + * @param $order_no + * @return array|false|\PDOStatement|string|Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public static function getNopayOrder($order_no){ + $order = self::where('order_no|id|pay_no',$order_no)->where("status",'0')->find(); + if(!$order)throw new \Exception("待支付订单不存在"); + return $order; + } + + + /** + * 检测支付开关(待扩展) + */ + public static function checkPaySwitch($pay_type,$user_id){ + + + } + + + /**订单支付前判断 + * @param $order_no + * @param $pay_type + * @return array|false|\PDOStatement|string|Model + */ + public function checkPay($order_no,$pay_type,$password = null){ + $order = self::getNopayOrder($order_no); + + //支付前判断 + //TODO通用判 + //... + $user = User::where('id',$order['user_id'])->find(); + if(!$user)throw new \Exception("支付用户异常"); + + //是否开通此支付方式 + self::checkPaySwitch($pay_type,$order['user_id']); + + //各自币种判断 + switch ($pay_type) { + case "wechat": //微信支付 + //.. + break; + case "alipay": //支付宝支付 + //... + break; + case "offline": //线下支付 + //.. + break; + case "yue": //余额支付 + //支付密码判断 +// $res = User::checkPayPassword($order['user_id'],$password,true); +// if(!$res['is_setting'])throw new \Exception("您还未设置支付密码,请您先去设置支付密码"); +// if(!$res['is_right'])throw new \Exception("支付密码错误"); + //判断余额是否充足 + //得到支付余额 + $total_amount = $order['totalprice'] ?:0; + //得到用户余额 + $money = $user['money']; + if(bccomp($money,$total_amount)==-1)throw new \Exception("当前余额不足以完成本次支付!"); + break; + default: + throw new \Exception("不支持的支付类型验证"); + } + + return true; + } + + + + + + + /**余额支付 + * @throws \Exception + */ + public function moneyPay($order_no,$method,$trans=false){ + $order = self::getNopayOrder($order_no); + //支付前判断 +// self::checkPay($order_no,'wallet'); + //判断逻辑 + if($trans){ + self::beginTrans(); + } + $res = true; + try{ + //事务逻辑 + if($order['totalprice']){ + $memo = "课时订单[ID:{$order['id']}]下单,支付余额{$order['totalprice']}"; + try{ + //扣除用户余额 + User::money(-$order['totalprice'], $order['user_id'], $memo,'class_order_pay' ,[ + 'order_id' => $order->id, + 'order_sn' => $order->order_sn, + ]); + }catch (\Exception $e){ + throw new \Exception("余额不足!"); + } + } + //调用订单支付成功事件 + $this->paySuccess($order_no,['platform'=>$method,'pay_type'=>'yue']); + if($trans){ + self::commitTrans(); + } + }catch (\Exception $e){ + if($trans){ + self::rollbackTrans(); + } + throw new \Exception($e->getMessage()); + } + return $res; + + } + + + + + + /**调用订单支付成功事件 + * @param $order_no + * @param $pay_type + * @param $price + * @param $price_check + * @param bool $trans + * @return bool + * @throws \Exception + */ + public function paySuccess($order_no,$notify=[],$price=0,$price_check=false,$trans=false){ + $order = self::getNopayOrder($order_no); + //金额校验 :第三方支付时回调入口判断 + if($price_check){ + if(bccomp($price,$order['totalprice'])==-1)throw new \Exception("支付金额与订单需要支付金额对应不上,回调失败!"); + } + //判断逻辑 + if($trans){ + self::beginTrans(); + } + $res = true; + try{ + //事务逻辑 + //不拆分订单,直接执行 + self::paySetData($order,$notify); + if($trans){ + self::commitTrans(); + } + }catch (\Exception $e){ + if($trans){ + self::rollbackTrans(); + } + throw new \Exception($e->getMessage()); + } + return $res; + } + + + public static function paySetData($order,$notify=[]){ + //订单支付更新 + $order = self::updatePay($order,$notify); + //生成订单一维码和二维码 + $order = self::buildCode($order); + //记录订单日志 + OrderLog::log($order['id'],"课程订单支付成功,核销码生成,可以正常约课",'user',$order['user_id']); + //调用支付成功事件 + $data = ['order' => $order]; + \think\Hook::listen('classes_order_payed_after', $data); + return true; + } + + + + + + public static function updatePay($order,$notify=[]){ + if(is_string($order)||is_numeric($order))$order = self::getNopayOrder($order); + $order->status ='3'; + $order->paytime = time(); + $order->pay_no = $notify['transaction_id'] ?? null; + $order->pay_json = $notify['payment_json'] ?? '{}'; + $order->pay_type = $notify['pay_type'] ?? 'yue'; + //如果订单创建时间大于预约时间,则等于预约时间 + if($order['createtime']>$order['starttime'])$order['createtime'] = $order['starttime']; + switch ($order->pay_type) { + case "offline": //线下付款,线上不需要支付 + + break; + + default: + $order->payprice = $notify['pay_fee'] ?? $order->totalprice; + } + $order->platform = $notify['platform'] ?? 'miniapp'; + $order->save(); + return $order; + } + + + + public static function buildCode($order){ + if(is_string($order)||is_numeric($order))$order = self::getNopayOrder($order); + //生成核销二维码和一维码 + //生成code + $vcode = en_code($order['id']). Random::alnum(); + $order['code'] = $vcode; + $params = [ + "type"=>"classes_verification", + "vcode" =>$vcode, + "id"=>$order['classes_lib_id'], + ]; + //$params生成get参数 + $params = http_build_query($params); + //生成二维码和一维码 + //二维码 + $order->codeimage = Common::getQrcode([ + 'text' => $params, + 'size' => 200, + ]); + //一维码 + $order->codeoneimage = Common::getBarcode([ + 'text' => $params, + 'size' => 200, + ]); + $order->save(); + return $order; + } + + + + + + } diff --git a/application/common/model/school/classes/order/OrderDetail.php b/application/common/model/school/classes/order/OrderDetail.php index d2e33a9..6e63d2c 100644 --- a/application/common/model/school/classes/order/OrderDetail.php +++ b/application/common/model/school/classes/order/OrderDetail.php @@ -2,6 +2,7 @@ namespace app\common\model\school\classes\order; +use app\common\model\dyqc\ManystoreShop; use think\Model; use traits\model\SoftDelete; @@ -74,9 +75,9 @@ class OrderDetail extends Model - public function order() + public function classorder() { - return $this->belongsTo('app\admin\model\school\classes\Order', 'classes_order_id', 'id', [], 'LEFT')->setEagerlyType(0); + return $this->belongsTo(Order::class, 'classes_order_id', 'id', [], 'LEFT')->setEagerlyType(0); } @@ -88,7 +89,7 @@ class OrderDetail extends Model public function shop() { - return $this->belongsTo('app\admin\model\manystore\Shop', 'shop_id', 'id', [], 'LEFT')->setEagerlyType(0); + return $this->belongsTo(ManystoreShop::class, 'shop_id', 'id', [], 'LEFT')->setEagerlyType(0); } @@ -96,4 +97,9 @@ class OrderDetail extends Model { return $this->belongsTo('app\admin\model\User', 'user_id', 'id', [], 'LEFT')->setEagerlyType(0); } + + public function teacher() + { + return $this->belongsTo('app\common\model\school\classes\Teacher', 'teacher_id', 'id', [], 'LEFT')->setEagerlyType(0); + } } diff --git a/application/common/model/school/classes/order/OrderLog.php b/application/common/model/school/classes/order/OrderLog.php index 9b516db..476d8ee 100644 --- a/application/common/model/school/classes/order/OrderLog.php +++ b/application/common/model/school/classes/order/OrderLog.php @@ -92,4 +92,49 @@ class OrderLog extends Model { return $this->belongsTo('app\admin\model\school\classes\Order', 'classes_order_id', 'id', [], 'LEFT')->setEagerlyType(0); } + + + + + + /**记录订单日志 + * @param $params + * @param bool $trans + * @throws \Exception + */ + public static function log($order,$mark='更新订单状态',$oper_type='user',$oper_id = 0,$trans=false){ + if(is_numeric($order))$order = Order::where('order_no|id|pay_no',$order)->find(); + if(!$order)throw new \Exception("找不到订单"); + //操作人信息(可扩展) + $data = [ + 'oper_type'=>$oper_type ?: 'user', + 'oper_id'=>$oper_id ?: $order['user_id'], + 'remark'=>$mark, + ]; + //判断逻辑 + if($trans){ + self::beginTrans(); + } + $res = true; + try{ + //事务逻辑 + $log_data = $order->toArray(); + $log_data["classes_order_id"] = $order['id']; + unset($log_data['id']); + unset($log_data['createtime']); + if($mark)$log_data['log_text'] = $mark; + $log = (new self); + $log->allowField(true)->save($log_data); + if($trans){ + self::commitTrans(); + } + }catch (\Exception $e){ + if($trans){ + self::rollbackTrans(); + } + throw new \Exception($e->getMessage()); + } + return $log; + } + }