__('Pay_type yue'), 'wechat' => __('Pay_type wechat')]; } public function getStatusList() { return ['-3' => __('Status -3'), '0' => __('Status 0'), '3' => __('Status 3'), '6' => __('Status 6'), '9' => __('Status 9')]; } public function getBeforeStatusList() { return ['-3' => __('Before_status -3'), '0' => __('Before_status 0'), '3' => __('Before_status 3'), '6' => __('Before_status 6'), '9' => __('Before_status 9')]; } public function getServerStatusList() { return ['0' => __('Server_status 0'), '3' => __('Server_status 3'), '6' => __('Server_status 6')]; } public function getResultStatusList() { return ['0' => __('Result_status 0'), '3' => __('Result_status 3'), '6' => __('Result_status 6')]; } public function getPayTypeTextAttr($value, $data) { $value = $value ? $value : (isset($data['pay_type']) ? $data['pay_type'] : ''); $list = $this->getPayTypeList(); return isset($list[$value]) ? $list[$value] : ''; } public function getStatusTextAttr($value, $data) { $value = $value ? $value : (isset($data['status']) ? $data['status'] : ''); $list = $this->getStatusList(); return isset($list[$value]) ? $list[$value] : ''; } public function getBeforeStatusTextAttr($value, $data) { $value = $value ? $value : (isset($data['before_status']) ? $data['before_status'] : ''); $list = $this->getBeforeStatusList(); return isset($list[$value]) ? $list[$value] : ''; } public function getServerStatusTextAttr($value, $data) { $value = $value ? $value : (isset($data['server_status']) ? $data['server_status'] : ''); $list = $this->getServerStatusList(); return isset($list[$value]) ? $list[$value] : ''; } public function getResultStatusTextAttr($value, $data) { $value = $value ? $value : (isset($data['result_status']) ? $data['result_status'] : ''); $list = $this->getResultStatusList(); return isset($list[$value]) ? $list[$value] : ''; } public function getCanceltimeTextAttr($value, $data) { $value = $value ? $value : (isset($data['canceltime']) ? $data['canceltime'] : ''); return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; } public function getPaytimeTextAttr($value, $data) { $value = $value ? $value : (isset($data['paytime']) ? $data['paytime'] : ''); return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; } public function getFinishtimeTextAttr($value, $data) { $value = $value ? $value : (isset($data['finishtime']) ? $data['finishtime'] : ''); return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; } public function getRefundtimeTextAttr($value, $data) { $value = $value ? $value : (isset($data['refundtime']) ? $data['refundtime'] : ''); return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; } protected function setCanceltimeAttr($value) { return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); } protected function setPaytimeAttr($value) { return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); } protected function setFinishtimeAttr($value) { return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); } protected function setRefundtimeAttr($value) { return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); } public function manystore() { return $this->belongsTo('app\admin\model\Manystore', 'manystore_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function user() { return $this->belongsTo('app\common\model\User', 'user_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function shop() { return $this->belongsTo(ManystoreShop::class, 'shop_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function lib() { return $this->belongsTo(ClassesLib::class, 'classes_lib_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function detail() { return $this->belongsTo(OrderDetail::class, 'classes_order_detail_id', 'id', [], 'LEFT')->setEagerlyType(0); } public function admin() { 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,$classes_lib_id = []){ $model = self::where('order_no|id|pay_no|code',$order_no); if($classes_lib_id)$model = $model->where("classes_lib_id","in",$classes_lib_id); $data = $model->find(); if(!$data) return $data; //加载订单详情 $data->detail->teacher; //订单用户 // $data->user; $data->user->visible(['id','nickname','mobile','avatar','realname']); //订单机构 $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||!$classes_lib_id )throw new \Exception("缺少必要参数"); //默认校验订单是否已创建 if($check){ //判断订单是否已创建 $order_info = self::where(['order_no'=>$order_no])->find(); if($order_info) throw new \Exception("订单已生成,如需重新下单请退出页面重新进入!"); } //校验订单参数 //课程是否存在并上架 $classes_lib_info = ClassesLib::where('id',$classes_lib_id)->find(); if(!$classes_lib_info || $classes_lib_info['status']!='1') throw new \Exception("该课程不存在或已下架!"); //用户存不存在 $user_info = User::where('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|code',$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, ]))['url']; //一维码 $order->codeoneimage = (Common::getBarcode([ 'text' => $params, 'size' => 200, ]))['url']; $order->save(); return $order; } /**得到基础条件 * @param $status * @param null $model * @param string $alisa */ public static function getBaseWhere($whereData = [], $model = null, $alisa = '',$with = false) { if (!$model) { $model = new static; if ($alisa&&!$with) $model = $model->alias($alisa); } if ($alisa) $alisa = $alisa . '.'; $tableFields = (new static)->getTableFields(); foreach ($tableFields as $fields) { if(in_array($fields, ['status','classes_lib_id']))continue; // if (isset($whereData[$fields]) && $whereData[$fields]) $model = $model->where("{$alisa}{$fields}", '=', $whereData[$fields]); if (isset($whereData[$fields]) && $whereData[$fields]){ if(is_array($whereData[$fields])){ $model = $model->where("{$alisa}{$fields}", $whereData[$fields][0], $whereData[$fields][1]); }else{ $model = $model->where("{$alisa}{$fields}", '=', $whereData[$fields]); } } } if (isset($whereData['status'])) $model = $model->where("{$alisa}status", 'in', $whereData['status']); if (isset($whereData['not_status'])) $model = $model->where("{$alisa}status", 'not in', $whereData['not_status']); if (isset($whereData['keywords'])&&$whereData['keywords']) $model = $model->where("{$alisa}order_no|{$alisa}pay_no|{$alisa}code", '=', $whereData['keywords']); if (isset($whereData['time'])&&$whereData['time']){ $model = $model->time($whereData['time']); } if (isset($whereData['user_id']) && $whereData['user_id']) $model = $model->where("{$alisa}user_id", '=', $whereData['user_id']); if (isset($whereData['classes_lib_ids']) && $whereData['classes_lib_ids']) $model = $model->where("{$alisa}classes_lib_id", 'in', $whereData['classes_lib_ids']); if (isset($whereData['classes_lib_id']) && $whereData['classes_lib_id']) $model = $model->where("{$alisa}classes_lib_id", 'in', $whereData['classes_lib_id']); return $model; } public static function allList($user_id,$page, $limit,$keywords,$status,$classes_lib_id=[]){ $with_field = [ 'user'=>['nickname','mobile','avatar','realname'], 'base'=>['*'], 'shop'=>['*'], 'detail'=>['*'], ]; $CANCEL = '-3'; $NOPAY = '0'; $PAYED = '3'; $REFUND = '6'; $FINISH = '9'; $alisa = (new self)->getWithAlisaName(); $sort = "field({$alisa}.status,'{$NOPAY}','{$PAYED}','{$FINISH}','{$REFUND}','{$CANCEL}') asc,{$alisa}.id desc"; $serch_where = ['status'=>$status,'user_id'=>$user_id,'keywords'=>$keywords,"classes_lib_id"=>$classes_lib_id]; // if($type)$serch_where['type'] = $type; return (new self)->getBaseList($serch_where, $page, $limit,$sort,$with_field); } public static function baseCount($where = []){ $CANCEL = '-3'; $NOPAY = '0'; $PAYED = '3'; $REFUND = '6'; $FINISH = '9'; $cancel_number = self::getBaseWhere(array_merge(['status'=>$CANCEL],$where))->count(); $nopay_number = self::getBaseWhere(array_merge(['status'=>$NOPAY],$where))->count(); $payed_number = self::getBaseWhere(array_merge(['status'=>$PAYED],$where))->count(); $retund_number = self::getBaseWhere(array_merge(['status'=>$REFUND],$where))->count(); $finish_number = self::getBaseWhere(array_merge(['status'=>$FINISH],$where))->count(); return compact('cancel_number','nopay_number','payed_number','retund_number','finish_number'); } /**订单数量统计 * @param int $user_id * @return array */ public static function orderCount($user_id = 0,$classes_lib_id=[]){ return self::baseCount(['user_id'=>$user_id,"classes_lib_id"=>$classes_lib_id]); } public static function workList($page, $limit,$keywords,$status,$classes_lib_id=[],$classes_lib_ids=[]){ $with_field = [ 'user'=>['nickname','mobile','avatar','realname'], 'base'=>['*'], 'shop'=>['*'], 'detail'=>['*'], ]; $CANCEL = '-3'; $NOPAY = '0'; $PAYED = '3'; $REFUND = '6'; $FINISH = '9'; $alisa = (new self)->getWithAlisaName(); $sort = "field({$alisa}.status,'{$NOPAY}','{$PAYED}','{$FINISH}','{$REFUND}','{$CANCEL}') asc,{$alisa}.id desc"; $serch_where = ['status'=>$status,'keywords'=>$keywords,"classes_lib_id"=>$classes_lib_id,"classes_lib_ids"=>$classes_lib_ids]; // if($type)$serch_where['type'] = $type; return (new self)->getBaseList($serch_where, $page, $limit,$sort,$with_field); } /**订单数量统计 * @param int $user_id * @return array */ public static function workCount($classes_lib_id=[],$classes_lib_ids=[]){ return self::baseCount(["classes_lib_id"=>$classes_lib_id,"classes_lib_ids"=>$classes_lib_ids]); } }