2509 lines
86 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\common\model\school\activity\order;
use addons\epay\library\Service;
use app\admin\model\Admin;
use app\admin\model\Manystore;
use app\common\model\BaseModel;
use app\common\model\school\activity\Activity;
use app\common\model\school\activity\Cate;
use app\common\model\school\activity\Refund;
use app\common\model\User;
use bw\Common;
use fast\Random;
use think\Cache;
use think\Model;
use traits\model\SoftDelete;
use Yansongda\Pay\Pay;
class Order extends BaseModel
{
use SoftDelete;
// 表名
protected $name = 'school_activity_order';
// 自动写入时间戳字段
protected $autoWriteTimestamp = 'integer';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
protected $deleteTime = 'deletetime';
// 追加属性
protected $append = [
'pay_type_text',
'status_text',
'before_status_text',
'server_status_text',
'suspend_status_text',
'canceltime_text',
'paytime_text',
'auth_time_text',
'reservation_time_text',
'finishtime_text',
'refundtime_text',
'auth_status_text',
'refundsendtime_text',
'cancel_last_seconds',
];
public function getCancelLastSecondsAttr($value, $data)
{
$value = $value ? $value : (isset($data['createtime']) ? $data['createtime'] : '');
if(!$value) return 0;
//活动未支付超时取消(秒)
$unpaid_activity_cancel_time = config("site.unpaid_activity_cancel_time");
//得到倒计时剩余秒数
$cancel_last_seconds = $unpaid_activity_cancel_time - (time() - $value);
return $cancel_last_seconds > 0 ? $cancel_last_seconds : 0;
}
public function getPayTypeList()
{
return ['yue' => __('Pay_type yue'), 'wechat' => __('Pay_type wechat')];
}
public function getStatusList()
{
return ['-3' => __('Status -3'), '0' => __('Status 0'), '2' => __('Status 2'), '3' => __('Status 3'), '4' => __('Status 4'), '5' => __('Status 5'), '6' => __('Status 6'),'7' => __('Status 7'), '9' => __('Status 9')];
}
public function getSuspendStatusList()
{
return ['0' => __('未挂起'), '1' => __('售后拒绝'), '2' => __('售后超时')];
}
public function getSuspendStatusTextAttr($value, $data)
{
$value = $value ? $value : (isset($data['suspend_status']) ? $data['suspend_status'] : '');
$list = $this->getSuspendStatusList();
return isset($list[$value]) ? $list[$value] : '';
}
public function getBeforeStatusList()
{
return ['-3' => __('Before_status -3'), '0' => __('Before_status 0'), '2' => __('Before_status 2'), '3' => __('Before_status 3'), '4' => __('Before_status 4'), '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 getAuthStatusList()
{
return ['0' => __('Auth_status 0'), '1' => __('Auth_status 1'), '2' => __('Auth_status 2')];
}
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 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 getAuthTimeTextAttr($value, $data)
{
$value = $value ? $value : (isset($data['auth_time']) ? $data['auth_time'] : '');
return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
}
public function getReservationTimeTextAttr($value, $data)
{
$value = $value ? $value : (isset($data['reservation_time']) ? $data['reservation_time'] : '');
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;
}
public function getAuthStatusTextAttr($value, $data)
{
$value = $value ? $value : (isset($data['auth_status']) ? $data['auth_status'] : '');
$list = $this->getAuthStatusList();
return isset($list[$value]) ? $list[$value] : '';
}
public function getRefundsendtimeTextAttr($value, $data)
{
$value = $value ? $value : (isset($data['refundsendtime']) ? $data['refundsendtime'] : '');
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 setAuthTimeAttr($value)
{
return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
}
protected function setReservationTimeAttr($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);
}
protected function setRefundsendtimeAttr($value)
{
return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
}
public function user()
{
return $this->belongsTo('app\common\model\User', 'user_id', 'id', [], 'LEFT')->setEagerlyType(0);
}
public function activity()
{
return $this->belongsTo(Activity::class, 'activity_id', 'id', [], 'LEFT')->setEagerlyType(0);
}
public function detail()
{
return $this->belongsTo(OrderDetail::class, 'activity_order_detail_id', 'id', [], 'LEFT')->setEagerlyType(0);
}
public function ordercode()
{
return $this->hasMany(OrderCode::class,'activity_order_id');
}
/**得到基础条件
* @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',"auth_status","server_status",'activity_id','user_id','activity_order_detail_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']) && $whereData['status']!=="") $model = $model->where("{$alisa}status", 'in', $whereData['status']);
if (isset($whereData['not_status']) && $whereData['not_status']!=="") $model = $model->where("{$alisa}status", 'not in', $whereData['not_status']);
if (isset($whereData['server_status']) && $whereData['server_status']!=="") $model = $model->where("{$alisa}server_status", 'in', $whereData['server_status']);
if (isset($whereData['not_server_status'])&& $whereData['not_server_status']!=="") $model = $model->where("{$alisa}server_status", 'not in', $whereData['not_server_status']);
if (isset($whereData['auth_status']) && $whereData['auth_status']!=="") $model = $model->where("{$alisa}auth_status", 'in', $whereData['auth_status']);
if (isset($whereData['not_auth_status'])&& $whereData['not_auth_status']!=="") $model = $model->where("{$alisa}auth_status", 'not in', $whereData['not_auth_status']);
if (isset($whereData['keywords'])&&$whereData['keywords']){
$model = $model->where("{$alisa}order_no|{$alisa}pay_no|user.nickname|user.realname|user.mobile|detail.title|detail.address|detail.address_detail", 'LIKE', "%{$whereData['keywords']}%" );
}
if (isset($whereData['time'])&&$whereData['time']){
$model = $model->time(["{$alisa}createtime",$whereData['time']]);
}
if (isset($whereData['user_id']) && $whereData['user_id']) $model = $model->where("{$alisa}user_id", '=', $whereData['user_id']);
if (isset($whereData['activity_ids']) && $whereData['activity_ids']) $model = $model->where("{$alisa}activity_id", 'in', $whereData['activity_ids']);
if (isset($whereData['activity_id']) && $whereData['activity_id']) $model = $model->where("{$alisa}activity_id", 'in', $whereData['activity_id']);
if (isset($whereData['activity_order_detail_id']) && $whereData['activity_order_detail_id']) $model = $model->where("{$alisa}activity_order_detail_id", 'in', $whereData['activity_order_detail_id']);
// if (isset($whereData['has_evaluate'])&&$whereData['has_evaluate']){
// //1查已评价 2查未评价
// if($whereData['has_evaluate'] == 1){
// //1查已评价
// $model = $model->where("{$alisa}classes_evaluate_id", '<>', 0);
// }else{
// //2查未评价
// $model = $model->whereExists(function ($query) use ($alisa) {
// $order_table_name = (new \app\common\model\school\classes\hourorder\Order())->getQuery()->getTable();
// $query->table($order_table_name)->where($order_table_name . '.classes_order_id=' . $alisa . 'id')->where('status', '=', '3');
// })->where("{$alisa}classes_evaluate_id", '=', 0);
//
// }
// }
return $model;
}
public static function allList($user_id,$page, $limit,$keywords,$status,$activity_id=[],$has_evaluate=0,$server_status="",$params=[]){
$with_field = [
'user'=>['nickname','mobile','avatar','realname'],
'base'=>['*'],
'detail'=>['*'],
'ordercode'=>['*'],
];
$CANCEL = '-3';
$NOPAY = '0';
$PAYED = '2';
$RESERV = '3';
$REFUND = '6';
$FINISH = '9';
$IN_SERVICE = '4';
$IN_REFUND = '5';
$alisa = (new self)->getWithAlisaName();
$sort = "field({$alisa}.status,'{$NOPAY}','{$PAYED}','{$RESERV}','{$FINISH}','{$REFUND}','{$IN_SERVICE}','{$CANCEL}' ,'{$IN_REFUND}') asc,{$alisa}.id desc";
$serch_where = ['status'=>$status,'user_id'=>$user_id,'keywords'=>$keywords,"activity_id"=>$activity_id,"has_evaluate"=>$has_evaluate,"server_status"=>$server_status];
// if($type)$serch_where['type'] = $type;
return (new self)->getBaseList(array_merge($serch_where,$params), $page, $limit,$sort,$with_field);
}
public static function baseCount($where = []){
$CANCEL = '-3';
$NOPAY = '0';
$PAYED = '2';
$RESERV = '3';
$REFUND = '6';
$FINISH = '9';
$IN_SERVICE = '4';
$IN_REFUND = '5';
$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();
$reserv_number = self::getBaseWhere(array_merge(['status'=>$RESERV],$where))->count();
$retund_number = self::getBaseWhere(array_merge(['status'=>$REFUND],$where))->count();
$in_retund_number = self::getBaseWhere(array_merge(['status'=>$IN_REFUND],$where))->count();
$finish_number = self::getBaseWhere(array_merge(['status'=>$FINISH],$where))->count();
$in_service_number = self::getBaseWhere(array_merge(['status'=>$IN_SERVICE],$where))->count();
return compact('cancel_number','nopay_number','payed_number','in_service_number','retund_number','in_retund_number','finish_number','reserv_number');
}
/**订单数量统计
* @param int $user_id
* @return array
*/
public static function orderCount($user_id = 0,$activity_id=[],$params=[]){
return self::baseCount(array_merge(['user_id'=>$user_id,"activity_id"=>$activity_id],$params));
}
public static function workList($user_id,$page, $limit,$keywords,$status,$activity_id=[],$activity_ids=[],$has_evaluate=0,$server_status="",$params=[]){
if(!$activity_ids) $activity_ids = [-5];
$with_field = [
'user'=>['nickname','mobile','avatar','realname'],
'base'=>['*'],
'detail'=>['*'],
'ordercode'=>['*'],
];
$CANCEL = '-3';
$NOPAY = '0';
$PAYED = '2';
$RESERV = '3';
$REFUND = '6';
$FINISH = '9';
$IN_SERVICE = '4';
$IN_REFUND = '5';
$alisa = (new self)->getWithAlisaName();
$sort = "field({$alisa}.status,'{$NOPAY}','{$PAYED}','{$RESERV}','{$FINISH}','{$REFUND}','{$IN_SERVICE}','{$CANCEL}','{$IN_REFUND}') asc,{$alisa}.id desc";
$serch_where = ['status'=>$status,'keywords'=>$keywords,"activity_id"=>$activity_id,"activity_ids"=>$activity_ids,"server_status"=>$server_status];
// if($type)$serch_where['type'] = $type;
return (new self)->getBaseList(array_merge($serch_where,$params), $page, $limit,$sort,$with_field);
}
/**订单数量统计
* @param int $user_id
* @return array
*/
public static function workCount($activity_id=[],$activity_ids=[],$params=[]){
if(!$activity_ids) $activity_ids = [-5];
return self::baseCount(array_merge(["activity_id"=>$activity_id,"activity_ids"=>$activity_ids],$params));
}
/**得到订单详情
* @param $order_no
*/
public static function getDetail($order_no,$activity_id = []){
//超时检测
self::timeoutCheck($order_no);
$model = self::where('order_no|id|pay_no',$order_no);
if($activity_id)$model = $model->where("activity_id","in",$activity_id);
$data = $model->find();
if(!$data) return $data;
//加载订单详情
$detail = $data->detail;
//订单用户
// $data->user;
$data->user->visible(['id','nickname','mobile','avatar','realname']);
$data["join_info"] = Activity::getJoininfo($data["activity_id"],$detail["stock"]);
//规格信息
$data->ordercode;
$cate_list = Cate::where("status",'1')->column("name","id");
$classes_cate_ids = $detail['cate_ids'];
$classes_cate = [];
foreach (explode(",", $classes_cate_ids) as $classes_cate_id){
if(isset($cate_list[$classes_cate_id]))$classes_cate[] = $cate_list[$classes_cate_id];
}
$data['classes_cate'] = $classes_cate;
return $data;
}
/**展示订单信息
* @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
* @return array|false|\PDOStatement|string|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function getOrder($order_no){
$order = self::where('order_no|id|pay_no',$order_no)->find();
if(!$order)throw new \Exception("订单不存在");
return $order;
}
/**
* 设置订单缓存
* @param $uid
* @param $data
* @return bool
*/
public static function setOrderCache($uid, $data)
{
//缓存名 = uid + order_no
$cacheNmae = 'activity_order_cache' . $uid . $data['order_no'];
// 缓存在3600秒之后过期
return Cache::set($cacheNmae, $data, config("site.unpaid_activity_expire_time"));
}
/**
* 得到订单缓存
* @param $uid
* @param $order_no
* @return mixed
*/
public static function getOrderCache($uid, $order_no)
{
//缓存名 = uid + order_no
$cacheNmae = 'activity_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 = 'activity_order_cache' . $uid . $order_no;
// 缓存在3600秒之后过期
return Cache::rm($cacheNmae);
}
/**订单校验
* @param $user_id 用户id
* @param $classes_lib_id 课程id
* @param $order_no 订单号
* @param $param 表单扩展参数
* @return bool
*/
public function orderVaild($user_id,$activity_id,$num, $order_no, $param,$check=false){
if(!$user_id||!$activity_id )throw new \Exception("缺少必要参数");
//更新活动状态
(new Activity)->updateStatus($activity_id);
//校验订单参数
//课程活动是否存在并上架
$activity = Activity::where('id',$activity_id)->find();
if(!$activity ) throw new \Exception("该活动不存在!");
//默认校验订单是否已创建
if($check){
if($activity['status']!='2' || $activity['auth_status']!='1') throw new \Exception("该活动不在可报名时间段!");
//此为创建订单时判断
//判断订单是否已创建
$order_info = self::where(['order_no'=>$order_no])->find();
if($order_info) throw new \Exception("订单已生成,如需重新下单请退出页面重新进入!");
//下单必须传规格id
$num = (int)$num;
if(!$num) throw new \Exception("请选择报名人数!");
$activity_max_people = config("site.activity_max_people");
if($activity_max_people){
if($num > $activity_max_people || $num < 1) throw new \Exception("单次报名人数不能超过{$activity_max_people}人!");
}
//判断数量是否超出库存
$sale = Activity::activitySale($activity_id,$num);//活动销量
if($sale > $activity['stock']){
//判断超了几人
$sub = $sale - $activity['stock'];
throw new \Exception("活动人数已超{$sub}人!无法下单!");
}
}
//用户存不存在
$user_info = User::where('id',$user_id)->find();
if(!$user_info) throw new \Exception("用户不存在!");
return true;
}
public static function getCost($user_id,$activity_id,$num=1,$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"],
];
//课程
$activity_info = Activity::get($activity_id);
$activity_info = $activity_info->toArray();
$activity_info["activity_id"] = $activity_id;
$activity_info["activity_order_id"] = 0;
$activity_info["refund_scale_json"] = "";
$activity_info["refund_status"]= "1";
$refund = Refund::get($activity_info["refund_id"]);
if($refund){
$activity_info["refund_scale_json"]= $refund["refund_scale_json"];
$activity_info["refund_status"]= $refund["status"];
}
// $activity_info['use_num'] = 0;//已使用课时
// $activity_info['sub_num'] = $activity_info['classes_num'];//剩余课时
// //单价 = 课程价格/总课时
// $activity_info["unit_price"] = bcdiv($activity_info['price'],$activity_info['classes_num'],2);
// $activity_info["used_price"] = 0;
$totalprice = bcmul($num,$activity_info["price"],2);
//组装订单下单数据
$order_data = [];
$order_data["user_id"] = $user_id;
$order_data["activity_id"] = $activity_id;
$order_data["activity_order_detail_id"] = 0;
$order_data["num"] = $num;
$order_data["fee_scale"] = $activity_info["fee_scale"];
$order_data["beforeprice"] = $totalprice;
$order_data["totalprice"] = $totalprice;
$order_data["payprice"] = 0;
$order_data["status"] = '0';
$order_data["settle_log_time"] = $activity_info["settlement_time"];
$order_data["last_time"] = $activity_info["end_time"] + config("site.activity_end_sales");
//根据手续费比例$activity_info["fee_scale"] 计算手续费
$order_data["fee_price"] = bcmul($order_data["totalprice"],$order_data["fee_scale"],2);
//手续费保底不能低于site.activity_settle_min_fee
$min_fee = config("site.activity_settle_min_fee");
$order_data["fee_price"] = $order_data["fee_price"] < $min_fee ? $min_fee : $order_data["fee_price"];
return compact('order_data','activity_info','user_data',"num");
}
/** 订单确认(订单计算)
* @param $user_id 下单用户
* @param $order_no 订单号(缓存标识)
* @param $activity_id 課程活动id
* @param $num 人数
* @param $param 額外参数(扩展用)
* @param bool $is_compute 是否重新计算订单
* @return array
*/
public function confirm($user_id, $activity_id,$num,$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,$activity_id,$num,$order_no, $param);
//订单支付信息
$price_info = $this->getCost($user_id,$activity_id,$num,$param);
//生成订单号
if (!$order_no) $order_no = get_order_sn();
//生成缓存
$data = compact('user_id', 'activity_id','num','param', 'order_no', 'price_info');
self::setOrderCache($user_id, $data);
}
\think\Hook::listen('activity_order_create_before', $data);
//下单数据展示
return $this->showInfo($order_no, $price_info);
}
/**得到待支付订单
* @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","in",['0',"-3"])->find();
if(!$order)throw new \Exception("待支付订单不存在");
return $order;
}
/**调用订单支付成功事件
* @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);
//释放结算订单
(new SettleLog)->timeoutSettleActivityCheck($order["activity_id"]);
// //如果需要快捷预约
// $classes_lib_spec_id = $order['classes_lib_spec_id'];
// if($classes_lib_spec_id){
// //记录代下单人信息
// $param = [
// "type" =>'2',
// "help_user_id" =>$order["user_id"] ,
// "help_type" => 'admin',
// ];
//
// //确认订单
// $res = (new \app\common\model\school\classes\hourorder\Order)->confirm($order["user_id"],$order['id'],null, $classes_lib_spec_id,$param, true);
// $remark = "订单支付完成同时快捷预约一个课时";
// //创建订单
// $result = (new \app\common\model\school\classes\hourorder\Order)->cacheCreateOrder($res['order_no'], $order["user_id"],$remark);
//
// }
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $res;
}
public static function updatePay($order,$notify=[]){
if(is_string($order)||is_numeric($order))$order = self::getNopayOrder($order);
$order->status ='2';
$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->sub_refundprice = $order->payprice; //剩余未退 = 支付金额
$order->save();
return $order;
}
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' => self::where("id",$order['id'])->find()];
\think\Hook::listen('activity_order_payed_after', $data);
//更新订单数据
self::statisticsAndUpdateClassesNumber($order);
return true;
}
public static function buildCode($order){
if(is_string($order)||is_numeric($order))$order = self::getNopayOrder($order);
$num = $order['num'];
for ($i=0;$i<$num;$i++){
$params = [
"activity_order_id"=>$order["id"],
"status" =>'3',
"activity_id"=>$order['activity_id'],
];
$orderCode = OrderCode::create($params);
$orderCode["code"] = en_code($orderCode["id"]);
$orderCode["miniurl"] = self::getMiniQrcodeLink(["order_id"=>$order['id'],"code"=>$orderCode["code"]]);
//生成核销二维码和一维码
// //生成二维码和一维码
// //二维码
// $orderCode->codeimage = (Common::getQrcode([
// 'text' => $orderCode["miniurl"],
// 'size' => 200,
// ]))['url'];
// //一维码
// $orderCode->codeoneimage = (Common::getBarcode([
// 'text' => $orderCode["miniurl"],
// 'size' => 200,
// ]))['url'];
$orderCode->save();
}
return $order;
}
public static function getPath($params=[],$expression = '{{KEYWORD}}'){
$path = config("site.activity_verification_url");
if(!$path) throw new \Exception("请先配置小程序路径");
// 参数替换
$template = Common::parsePrintTemplateString($path,$params,$expression);
return $template;
}
public static function getMiniQrcodeLink($params=[],$expression = '{{KEYWORD}}',$qrcode=false){
$path = self::getPath($params,$expression);
//如果路径开头有斜杠则去除
if(substr($path,0,1) == "/"){
$path = substr($path,1);
}
//解析该路径获取url参数和除去参数的url
$url_params = [];
$url = "";
if(strpos($path,"?") !== false){
$url_params = explode("?",$path);
$url = $url_params[0];
$url_params = $url_params[1];
}else{
$url = $path;
}
$q_params = [];
//生成小程序二维码
$query = $url_params;
$q_params["path"] = $url;
if($query)$q_params["query"] = $query;
// 实例对应的接口对象
$scheme = new \WeMini\Scheme(Service::wechatConfig());
$res= $scheme->urlLink($q_params);
if(!isset($res["url_link"]))throw new \Exception("生成小程序二维码失败");
$url_link = $res["url_link"];
if(!$qrcode)return $url_link;
//链接生成二维码
//二维码
$response = Common::getQrcode([
'text' => $url_link,
'size' => 200,
],false,false,true);
//全返回
return compact("url_link","response");
}
/**
* 根据缓存创建订单
*/
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 {
//'classes_activity_id','classes_activity_item_id'
//1订单执行创建
$order = $this->createOrder($user_id,$orderInfo['activity_id'],$orderInfo['num'],$order_no,$orderInfo['param'],$remark);
$orderitem = $order->detail;
//如果是免费订单,则直接调用支付完成
if ($orderitem['feel'] == '1' || $order['totalprice'] == 0) {
//调用订单支付成功事件
$this->paySuccess($order_no,['platform'=>"miniapp",'pay_type'=>'yue']);
}
//5删除缓存
self::deleteOrderCache($user_id, $order_no);
if ($trans) {
self::commitTrans();
}
} catch (\Exception $e) {
if ($trans) {
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile() . $e->getLine());
}
return self::showInfo($order_no);
}
public function createOrder($user_id,$activity_id,$num,$order_no,$param,$remark='',$other_params=[]){
$this->orderVaild($user_id,$activity_id,$num, $order_no, $param,true);
//订单支付信息
$order_info = self::getCost($user_id,$activity_id,$num,$param,$other_params,true);
//组装订单数据 compact('order_data','activity_info','user_data',"activity_info");
$order_data = $order_info['order_data'];
$order_data["order_no"] = $order_no;
$res1 = self::create($order_data);
if (!$res1) throw new \Exception('创建订单失败');
$activity_info = $order_info["activity_info"];
if(!$activity_info)throw new \Exception('订单未选活动!');
//課程详情
$activity_info = $order_info['activity_info'];
$order_detail_data = [];
$order_detail_data = array_merge($order_detail_data,$activity_info);
$order_detail_data["activity_id"] = $activity_info['id'];
$order_detail_data["activity_order_id"] = $res1['id'];
unset($order_detail_data['id']);
unset($order_detail_data['createtime']);
$orderDetail = (new OrderDetail());
$orderDetail->allowField(true)->save($order_detail_data);
// $order_item_data = [];
// $order_item_data = array_merge($order_item_data,$classes_activity_item->toArray());
// $order_item_data["classes_activity_item_id"] = $classes_activity_item['id'];
// $order_item_data["classes_activity_order_id"] = $res1['id'];
//
// unset($order_item_data['id']);
// unset($order_item_data['createtime']);
// $orderItem = (new OrderItem());
// $orderItem->allowField(true)->save($order_item_data);
//更新订单详情id
$res1->activity_order_detail_id = $orderDetail->id;
$res1->save();
//记录订单日志
OrderLog::log($res1['id'],"活动订单创建成功,等待下一步操作(如果付费需去支付)",'user',$user_id);
//7事件
$data = ['order' => self::where("id",$res1['id'])->find()];
\think\Hook::listen('activity_order_create_after', $data);
//更新订单数据
self::statisticsAndUpdateClassesNumber($res1);
return $res1;
}
//统计并更新活动数等相关统计数据
public static function statisticsAndUpdateClassesNumber($order){
if(is_string($order)||is_numeric($order))$order = self::getOrder($order);
// $detail = $order->detail;
// if(!$detail)throw new \think\Exception("订单信息缺失!");
//
// //课程下单时已核销人数更新
// $lib = $order->activity;
// if($lib){
//
// }
//将课程信息和课时信息同步到所有已下单的订单信息中
(new Activity)->update_classes($order["activity_id"]);
//如果有评价执行评价更新
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通用判
$this->orderVaild($order['user_id'],$order['activity_id'],$order['num'],$order['order_no'], []);
//...
$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;
}
/** 课时订单操作权限检测
* @param $order 订单
* @param $oper_id 操作人id
* @param $oper_type 操作人类型user-用户或员工admin-管理员
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function checkOptionAuth($classes_order_id,$oper_id,$oper_type,$only_user=false,$only_shop=false,$only_admin=false,$only_shop_user=false){
//课程是否存在并上架
$classesOrder = Order::where("id",$classes_order_id)->find();
if(!$classesOrder)throw new \Exception("订单不存在!");
switch ($oper_type) {
case 'user':
if($only_admin)throw new \Exception("您无权操作该订单!");
//自己或操作员
if($oper_id != $classesOrder["user_id"]){
if($only_user) throw new \Exception("您无权操作该订单!");
//说明是操作员
$help_user_info = User::where('id',$oper_id)->find();
if(!$help_user_info) throw new \Exception("代下单员工不存在!");
$classes_activity_ids = (new Activity())->getActivityAuthIds($oper_id);
//判断当前订单课程是否在此课程授权范围内
if(!in_array($classesOrder["activity_id"],$classes_activity_ids)) throw new \Exception("该活动不在您的授权范围内,无法代操作!");
}else{
if($only_shop_user) throw new \Exception("您无权操作该订单!");
$classes_activity_ids = (new Activity())->getActivityAuthIds($oper_id);
//不是员工并且想操作只有机构能操作的单
if(!in_array($classesOrder["activity_id"],$classes_activity_ids) && $only_shop)throw new \Exception("您无权操作该订单!");
}
break;
case 'admin':
$admin_info = Admin::where('id',$oper_id)->find();
if(!$admin_info) throw new \Exception("代下单管理员不存在!");
break;
case 'shop':
if($only_admin)throw new \Exception("您无权操作该订单!");
if($only_user) throw new \Exception("您无权操作该订单!");
$admin_info = Manystore::where('id',$oper_id)->find();
if(!$admin_info) throw new \Exception("代下单管理员不存在!");
$classes_activity_ids = (new Activity())->getActivityAuthIds($admin_info["user_id"]);
//判断当前订单课程是否在此课程授权范围内
if(!in_array($classesOrder["activity_id"],$classes_activity_ids)) throw new \Exception("该活动不在您的授权范围内,无法代操作!");
break;
default:
throw new \Exception("请选择正确的代下单类型!");
}
}
/** 检测订单完成状态
* @param $order
* @return void
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function statisticsAndUpdateOrderFinish($order){
if(is_string($order)||is_numeric($order))$order = self::getOrder($order);
$detail = $order->detail;
if(!$detail)throw new \think\Exception("订单信息缺失!");
//得到当前订单的所有已完成课时订单
$hourorderOrderCount = OrderCode::where("status","=",'6')
->where("activity_order_id","=",$order["id"])
->count();
//可完成的订单
if(in_array($order["status"],["2",'3']) && (in_array($order["server_status"],['0'])) && !$order['finishtime']){
//判定是否达成完成条件
//条件:课程课时已全部完成
if($hourorderOrderCount >= $order->num){
//执行订单完成的更新逻辑
self::updateFinish($order);
}elseif($order["status"] == "2"){
//更新订单状态成核销中
$order["status"] = '3';
$order["reservation_time"] = time();
$order->save();
}
}
}
/** 更新订单成已完成
* @param $order
* @return void
*/
public static function updateFinish($order){
if(is_string($order)||is_numeric($order))$order = self::getOrder($order);
//检测未核销订单,强制核销
$orderCodes = OrderCode::where("status","=",'3')
->where("activity_order_id","=",$order["id"])
->select();
foreach ($orderCodes as $k=>$orderCode){
//强制核销
(new OrderCode)->forceVerification($orderCode["code"],"admin",0,false);
}
//更新订单
$order["status"] = '9';
$order["finishtime"] = time();
$order->save();
//记录订单日志
OrderLog::log($order['id'],"活动订单完成,人数已全部核销",'user',$order['user_id']);
//释放结算订单
(new SettleLog)->timeoutSettleActivityCheck($order["activity_id"]);
//调用支付成功事件
$data = ['order' => $order];
\think\Hook::listen('activity_order_finish_after', $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 getHaveCancelFreeOrder($order_no,$check=false){
// $where = [self::STATUS_NOPAY,self::STATUS_PAYED];
$order = self::where('order_no|id|pay_no',$order_no)->find();
$detail = $order->detail;
if(!$detail) throw new \Exception("订单信息缺失!");
if(!$order)throw new \Exception("订单信息缺失!");
//非免费单进行中无法取消
if($order['totalprice'] != 0){
//非免费单只有未支付的单才可以取消
if($order['status'] != '0'){
throw new \Exception("只有未支付的单才可以取消!");
}
}else{
if($check && !in_array($order['status'],['0','2'])){
throw new \Exception("免费单只有进行中订单可取消,已取消请忽重复取消!");
}
}
if(!$check) return $order;
//活动开始后不可取消
$time = time();
if ($detail['start_time'] < $time) throw new \Exception("活动开始后不可取消!");
return $order;
}
/**更新免费订单取消状态以及未支付取消
* @param $order
* @return array|false|\PDOStatement|string|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function updateFreeCancel($order,$check=false){
if(is_string($order))$order = self::getHaveCancelFreeOrder($order,$check);
$order->status = "-3";//refund_status
$order->canceltime = time();
$order->save();
return $order;
}
/**免费订单取消以及未支付取消
* @param $order_no
* @param int $user_id
* @param bool $check
* @param bool $trans
* @return bool
* @throws \Exception
*/
public function freeCancel($order_no,$user_id=0,$check=false,$oper_type='user',$oper_id=0,$trans=false){
//得到可取消订单
$order = self::getHaveCancelFreeOrder($order_no,$check);
if($check){
//用户操作权限检测
self::checkOptionAuth($order['id'],$user_id ?: $oper_id,$oper_type);
}
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//事务逻辑
//更新订单取消状态
$order = self::updateFreeCancel($order,$check);
//插入订单取消日志
if(!$user_id ||$order["user_id"] !=$user_id ){
OrderLog::log($order['id'],"[系统操作]活动订单取消成功",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}else{
OrderLog::log($order['id'],"活动订单取消成功",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}
//调用订单取消事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$user_id,"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_cancel_after', $data);
//执行课时数更新
$res1 = self::statisticsAndUpdateClassesNumber($order['id']);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $res1;
}
/**
* 超时取消检测(未支付取消)
*/
public static function timeoutCheck($order_id=0,$trans = false){
$count = 0;
$unpaid_order_expire_time = config("site.unpaid_activity_cancel_time");
if(!$unpaid_order_expire_time|| $unpaid_order_expire_time < 0)return $count;
$unpaid_order_expire_time = time() - $unpaid_order_expire_time;
//得到所有过期的队列
$model = self::where("status",'in',['0'])->where("createtime","<=",$unpaid_order_expire_time);
if($order_id)$model = $model->where("id|order_no",$order_id);
$list = $model->select();
if ($trans) {
self::beginTrans();
}
try {
foreach ($list as $order)
{
//取消订单
(new self)->freeCancel($order['id'],0,false,'admin',0);
$count++;
}
if ($trans) {
self::commitTrans();
}
} catch (\Exception $e) {
if ($trans) {
self::rollbackTrans();
}
throw new \Exception($e->getMessage());
}
return $count;
}
/**得到可取消的付费订单
* @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 getHaveCancelPaidOrder($order_no,$check=true){
// $where = [self::STATUS_NOPAY,self::STATUS_PAYED];
$order = self::where('order_no|id|pay_no',$order_no)->find();
$detail = $order->detail;
if(!$detail) throw new \Exception("订单信息缺失!");
if(!$order)throw new \Exception("订单信息缺失!");
//非免费单进行中无法取消
if($order['totalprice'] != 0){
//非免费单只有未退款可退
if(!in_array($order['status'],["2","3","9"])){
throw new \Exception("只有已报名和核销中的单才可以取消!");
}
}else{
throw new \Exception("免费单请走免费订单取消接口!");
}
if($check){
//根据不同退款策略判断当前时间点是否可取消并退款
$refund_status = $detail["refund_status"];
//活动开始后不可取消,只能走售后
$time = time();
switch ($refund_status){
case "1": //不退款
throw new \Exception("当前活动不支持取消退款,请走售后流程!");
break;
case "3": //开始前退
case "5": //随时退
if ($detail['start_time'] < $time) throw new \Exception("活动开始后不可取消,请走售后流程!");
break;
case "7": //前12小时退
if ($detail['start_time'] < $time) throw new \Exception("活动开始后不可取消,请走售后流程!");
break;
case "9": //前24小时退
if ($detail['start_time'] < $time) throw new \Exception("活动开始后不可取消,请走售后流程!");
break;
case "11": //前24小时退
if ($detail['start_time'] < $time) throw new \Exception("活动开始后不可取消,请走售后流程!");
break;
default:
throw new \Exception("不支持的退款策略,请走售后流程!");
}
}
return $order;
}
/**更新付费订单取消状态
* @param $order
* @return array|false|\PDOStatement|string|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function updatePaidCancel($order,$check=true){
if(is_string($order))$order = self::getHaveCancelPaidOrder($order,$check);
$order->status = "-3";//refund_status
$order->canceltime = time();
$order->save();
//将所有资金结算记录作废
$data = [
"status" =>"-1",
"canceltime" => time(),
];
SettleLog::where("activity_order_id",$order["id"])->where("status","in",['1','2'])->update($data);
return $order;
}
/**得到可审核订单
* @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 getHaveExamineOrder($order_no){
// $where = [self::STATUS_NOPAY,self::STATUS_PAYED];
$order = self::where('order_no|id|pay_no',$order_no)->where("status","in",["-3",'4'])->find();
if(!$order)throw new \Exception("不是待审核的订单!");
return $order;
}
/**更新订单结算中状态
* @param $order
* @return array|false|\PDOStatement|string|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function updateExamineFailSettlement($order,$reason="",$oper_id = 0,$oper_type='user'){
if(is_string($order))$order = self::getHaveExamineOrder($order);
$order->before_status = $order->status;
$order->status = "5";
$order->auth_status = "1";
$order->server_status = "6";
$order->auth_time = time();
$order->refundsendtime = time();
$order->reason = $reason;
$order->auth_user_id = $oper_id;
$order->auth_type = $oper_type;
$order->save();
return $order;
}
/**付费订单取消
* @param $order_no
* @param int $user_id
* @param bool $check
* @param bool $trans
* @return bool
* @throws \Exception
*/
public function paidCancel($order_no,$user_id=0,$check=false,$oper_type='user',$oper_id=0,$activity_cancel=false,$trans=false){
//得到可取消订单
$order = self::getHaveCancelPaidOrder($order_no,$check);
if($check){
//用户操作权限检测
self::checkOptionAuth($order['id'],$user_id ?: $oper_id,$oper_type);
}
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//事务逻辑
//更新订单取消状态
$order = self::updatePaidCancel($order,$check);
//插入订单取消日志
if(!$user_id ||$order["user_id"] !=$user_id ){
OrderLog::log($order['id'],"[系统操作]活动订单取消成功,款项在一个工作日内将自动退还",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}else{
OrderLog::log($order['id'],"活动订单取消成功,款项在一个工作日内将自动退还",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}
//调用订单取消事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$user_id,"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_cancel_after', $data);
//执行课时数更新
$res1 = self::statisticsAndUpdateClassesNumber($order['id']);
//执行退款
//调用退款发起
//更新订单状态
$reason = "活动取消自动退款";
$order = self::updateExamineFailSettlement($order['order_no'],"",$user_id ?: $oper_id,$oper_type);
//审核失败逻辑
OrderLog::log($order['id'],"活动取消退款,原因;{$reason},如有退款额度,该活动单将自动退款以便重新下单(没有请忽略)",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
//调用订单事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$user_id,"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_auth_fail_after', $data);
//计算自动退款额度
$refundprice = $this->countRefundAmount($order["id"],$activity_cancel);
if($refundprice>0){
//执行退款
self::orderRefund($order['order_no'],$refundprice,$oper_type,$oper_id);
}
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $res1;
}
/** 如果是自动退款,根据退款策略计算应退款额
* @param $order_id
* @param $trans
* @return true
* @throws \Exception
*/
public function countRefundAmount($order_id,$activity_cancel=false,$trans=false){
$price = 0;
$order = self::where('id',$order_id)->find();
if(!$order) return $price;
$detail = $order->detail;
if(!$detail) return $price;
//
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//根据不同退款策略判断当前时间点是否可取消并退款
$refund_status = $detail["refund_status"];
//活动开始后不可取消,只能走售后
$time = time();
if($activity_cancel){
//活动取消退全款
$price = $order["sub_refundprice"];
}else{
switch ($refund_status){
case "1": //不退款
throw new \Exception("当前活动不支持取消退款,请走售后流程!");
break;
case "3": //开始前退
case "5": //随时退 退全款
$price = $order["sub_refundprice"];
break;
case "7": //前12小时退
//还没到开始前12小时退100%
//过了开始前12小时退50%
if($time < ($detail["start_time"]-(12*3600))){
$price = $order["sub_refundprice"];
}else{
$price = bcdiv($order["sub_refundprice"],2,2);
}
break;
case "9": //前24小时退
//还没到开始前24小时退100%
//过了开始前24小时退50%
if($time < ($detail["start_time"]-(24*3600))){
$price = $order["sub_refundprice"];
}else{
$price = bcdiv($order["sub_refundprice"],2,2);
}
break;
case "11": //前48小时退
//还没到开始前48小时退100%
//过了开始前48小时退50%
if($time < ($detail["start_time"]-(48*3600))){
$price = $order["sub_refundprice"];
}else{
$price = bcdiv($order["sub_refundprice"],2,2);
}
break;
default:
throw new \Exception("不支持的退款策略,请走售后流程!");
}
}
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $price;
}
/**得到可直接退款订单(正常取消退全款非售后)
* @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 getHaveRefundOrder($refund_sn){
// $where = [self::STATUS_NOPAY,self::STATUS_PAYED];
$order = self::where('refund_no|order_no|id',$refund_sn)->where('status','5')->where("sub_refundprice",">",0)->find();
if(!$order)throw new \Exception("不是可直接退款的订单!");
return $order;
}
/**预约订单退款(退全款)
* @param $order
* @param $refund_money
* @param $trans
*/
public static function orderRefund($order,$refund_money,$oper_type='user',$oper_id=0,$trans=false,$admin=false){
if(is_numeric($order))$order = self::getHaveRefundOrder($order);
if(!$order)throw new \Exception("找不到订单");
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//生成退款单号
if(!$order['refund_no']){
$order['refund_no'] = get_order_sn();
// $order->save();
}
if(!$refund_money)$refund_money = $order["total_refundprice"] ?: $order['sub_refundprice'];
if($refund_money<=0)$refund_money = 0;
if(!$refund_money)throw new \Exception("退款金额异常!");
//保存应退款额
$order->total_refundprice = $refund_money;
$order->save();
//事务逻辑
switch ($order['pay_type']) {
case "wechat": //微信退款
self::wechatRefund($order,$refund_money,$oper_type,$oper_id);
break;
// case "alipay": //支付宝退款
// self::alipayRefund($order,$refund_money,$oper_type,$oper_id);
// break;
// case "wallet": //钱包支付退款
// self::walletRefund($order,$refund_money,$oper_type,$oper_id);
// break;
// case "offline": //线下支付退款
//
// break;
default:
throw new \Exception("订单币种异常!");
}
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage());
}
return $res;
}
/**微信退款
* @param $order
* @param $refund_money
*/
public static function wechatRefund($order,$refund_money,$refund_desc="",$oper_type='user',$oper_id=0,$trans=false){
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//事务逻辑
//创建订单退款订单
$classesorder = $order;
$detail = $order->detail;
//执行微信退款sdk
$order_data = [
'out_trade_no' => $classesorder->order_no
];
$total_fee = $classesorder->payprice * 100;
$refund_fee = $refund_money * 100;
$order_data = array_merge($order_data, [
'out_refund_no' => $order->refund_no,
'total_fee' => $total_fee,
'refund_fee' => $refund_fee,
'refund_desc' => $refund_desc ?: "活动[{$detail["title"]}]订单[ID:{$classesorder['id']}]退款{$refund_money}已到账户",
]);
$config = Service::getConfig('wechat',[],$classesorder->platform);
$notify_url = request()->domain() . '/api/school.newactivity.pay/notifyr/payment/' . $classesorder->pay_type . '/platform/' . $classesorder->platform;
$config['notify_url'] = $notify_url;
$pay = Pay::wechat($config);
// throw new \Exception($trans."111测试错误".$order["status"]);
$result = $pay->refund($order_data);
\think\Log::write('refund-result' . json_encode($result));
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
$res = true;
} else {
throw new \Exception($result['return_msg']);
}
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $res;
}
/**同意并发放退款金额
* @param $order 退款单号
* @return array|false|\PDOStatement|string|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function updateRefundOrder($order,$price=0,$pay_json=[]){
if(is_string($order))$order = self::getHaveRefundOrder($order);
// //如果$price为0尝试从$pay_json退款回调中获取退款金额
// if(!$price && $pay_json){
// if(isset($pay_json['refund_fee']))$price = bcdiv($pay_json['refund_fee'],100,2);
// }
// $order->before_status = $order->status;//refund_status
$order->status = "6";
$order->server_status = "6";
$order->real_refundprice = bcadd($order->real_refundprice ?:'0', $price?:$order->total_refundprice ,2);
$order->sub_refundprice = bcsub($order->sub_refundprice ?:'0', $price?:$order->total_refundprice ,2);
$order->refundtime = time();
$order->refund_json = json_encode($pay_json);
$order->save();
//报废旧的结算单
SettleLog::where("activity_order_id",$order['id'])
->where("status" ,"in",["1","2"])
->update([
"status"=>"-1",
"canceltime" => time()
]);
//若存在剩余金额,结算给发布者
if($order->sub_refundprice > $order->fee_scale){
//插入新结算单
// (new SettleLog)->generatorLog($order['id']);
//对活动订单进行结算记录插入
(new SettleLog)->timeoutSettleActivityCheck($order["activity_id"]);
}
return $order;
}
/**退款成功处理逻辑(需修改)
* @param $refund_sn
* @param bool $trans
* @return bool
* @throws \Exception
*/
public static function refundSuccess($refund_sn,$refund_json=[],$price=0,$trans=false){
//得到机构售后提交确认订单
$order = self::getHaveRefundOrder($refund_sn);
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//事务逻辑
//更新订单状态为同意
$order = self::updateRefundOrder($refund_sn,$price,$refund_json);
//插入订单日志
OrderLog::log($order['id'],"活动订单退款已原路退回", 'admin', 0);
//执行课时数更新
$res1 = self::statisticsAndUpdateClassesNumber($order['id']);
//调用订单取消事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$order['user_id'],"oper_type"=>'admin',"oper_id"=>0];
\think\Hook::listen('activity_order_refund_success_after', $data);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage());
}
return $res;
}
/**订单退款失败记录
* @param $order
* @param $refund_money
* @param $trans
*/
public static function orderRefundFail($order,$msg,$oper_type='user',$oper_id=0,$trans=false,$admin=false){
if(is_numeric($order))$order = self::getOrder($order);
if(!$order)throw new \Exception("找不到订单");
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//事务逻辑
$order->refund_error = $msg;
$order->save();
//插入订单日志
OrderLog::log($order['id'],"活动订单退款失败:".$msg, $oper_type, $oper_id);
//调用订单取消事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$order['user_id'],"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_refund_fail_after', $data);
if($trans){
self::commitTrans();
}
}catch (\Throwable $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage());
}
return $res;
}
/**得到可直接退款订单(正常取消退全款非售后)
* @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 getHaveAfterSalesOrder($refund_sn,$check=true){
// $where = [self::STATUS_NOPAY,self::STATUS_PAYED];
$order = self::where('refund_no|order_no|id',$refund_sn)->where('status',"in",["2","3","9"])->where("sub_refundprice",">",0)->find();
if(!$order)throw new \Exception("不是可直接退款的订单!");
$detail = $order->detail;
if(!$detail)throw new \Exception("找不到订单详情!");
if($check){
if(!in_array($order['status'],["2","3","9"])) throw new \Exception("不是可申请售后的订单!");
//申请售后必须是活动结束后n秒内
$last_time = $detail['end_time'] + config('site.activity_end_sales');
if(time() > $last_time) throw new \Exception("已超过发起售后的最后期限!");
}
return $order;
}
public function updateAfterSales($order,$total_refundprice,$auth_reason,$check=true){
if(is_numeric($order))$order = self::getHaveAfterSalesOrder($order,$check);
$order["before_status"] = $order["status"];
$order["status"] = "4";
$order["server_status"] = "3";
$order["auth_time"] = time();
$order["auth_status"] = 0;
$order["auth_reason"] = $auth_reason;
$order["total_refundprice"] = $total_refundprice;
$order["first_refundprice"] = $total_refundprice;
$order->save();
}
/** 发起售后
* @param $order_no
* @param $user_id
* @param $trans
* @return true
* @throws \Exception
*/
public function afterSales($order,$num,$auth_reason,$user_id ,$check=true,$oper_type='user',$oper_id=0,$trans=false){
if(is_numeric($order))$order = self::getHaveAfterSalesOrder($order,$check);
if(!$order)throw new \Exception("找不到订单");
if($check){
//用户操作权限检测
self::checkOptionAuth($order['id'],$user_id ?: $oper_id,$oper_type);
}
$detail = $order->detail;
if(!$detail)throw new \Exception("找不到订单详情!");
if(!$auth_reason) throw new \Exception("请填写申请原因");
//$num至少要1至多要订单数量
if($num < 1 || $num > $order['num']) throw new \Exception("数量错误");
$total_refundprice = bcmul($detail['price'],$num,2);
if($total_refundprice < 0.01 || $total_refundprice > $order['sub_refundprice']) throw new \Exception("退款金额错误");
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//设置订单申请状态
$this->updateAfterSales($order,$total_refundprice,$auth_reason,$check);
//订单日志
OrderLog::log($order['id'],"活动订单申请售后:".$auth_reason, $oper_type, $oper_id);
//调用订单取消事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$order['user_id'],"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_after_sales_after', $data);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $res;
}
public static function getHaveShopConfirmationOrder($refund_sn,$check=true){
// $where = [self::STATUS_NOPAY,self::STATUS_PAYED];
$order = self::where('refund_no|order_no|id',$refund_sn)->where('status',"in",["4","7"])->where("sub_refundprice",">",0)->find();
if(!$order)throw new \Exception("不是可直接退款的订单!");
$detail = $order->detail;
if(!$detail)throw new \Exception("找不到订单详情!");
if($check){
}
return $order;
}
/**机构同意并确认价格
* @param $order
* @return array|false|\PDOStatement|string|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function updateShopConfirmationOrder($order,$price){
if(is_string($order))$order = self::getHaveShopConfirmationOrder($order);
$order->status = "5";//refund_status
$order->server_status = "6";
$order->auth_status = 1;
$order['suspend_status'] = '1';
$order->total_refundprice = $price;
$order->refundtime = time();
$order->save();
return $order;
}
/**机构拒绝售后
* @param $order
* @return array|false|\PDOStatement|string|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public static function updateShopConfirmationFailOrder($order,$reason){
if(is_string($order))$order = self::getHaveShopConfirmationOrder($order);
$order["status"] = "7";//refund_status
$order["server_status"] = "6";
$order["auth_status"] = 2;
$order["reason"] = $reason;
$order['suspend_status'] = '1';
$order->save();
return $order;
}
/**发布者售后提交确认信息
* @param $order_no
* @param int $user_id
* @param string $status yes同意 no拒绝
*@param string $price 商定价格
*@param string $reject_reason 拒绝原因
* @param bool $check
* @param bool $trans
* @return bool
* @throws \Exception
*/
public function shopConfirmation($order_no,$status,$price,$reject_reason,$user_id=0,$check=false,$oper_type='user',$oper_id=0,$trans=false){
//得到机构售后提交确认订单
$order = self::getHaveShopConfirmationOrder($order_no);
$this->timeoutSuspension($order['id'],$trans);
//得到机构售后提交确认订单
$order = self::getHaveShopConfirmationOrder($order_no);
if($check){
//用户操作权限检测
Order::checkOptionAuth($order['id'],$user_id ?: $oper_id,$oper_type,false,true);
}
$classesorder = $order;
if(!$classesorder)throw new \Exception("订单不存在!");
switch ($status){
case 'yes':
//同意
//验证价格正确性
//金额必须小于等于剩余未退全额
$price = floatval($price);
if($price > $classesorder['sub_refundprice']){
throw new \Exception("价格必须小于等于剩余应退全额!");
}
//数据修正大于剩余未退全额 则等于剩余未退
$price = $price > $classesorder['sub_refundprice'] ? $classesorder['sub_refundprice'] : $price;
if($price<=0)throw new \Exception("同意退款必须大于0");
//如果非全额退款,剩余金额必须大于订单支付时的手续费
if($price < $classesorder['sub_refundprice']){
$sub_price = bcsub($classesorder['sub_refundprice'] , $price,2);
if($sub_price <= $classesorder['fee_price']){
throw new \Exception("如果未退全款,剩余未退金额必须大于订单支付时的手续费{$classesorder['fee_price']}");
}
}
break;
case 'no':
//拒绝
//拒绝原因必填
if(!$reject_reason)throw new \Exception("拒绝原因必填!");
break;
default:
throw new \Exception("参数错误!");
}
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//事务逻辑
//更新售后为取消状态
switch ($status){
case 'yes':
//同意
//更新订单状态为同意
$order = self::updateShopConfirmationOrder($order,$price);
//插入订单日志
if(!$user_id ||$order["user_id"] !=$user_id ){
OrderLog::log($order['id'],"[系统操作]活动订单售后已处理,用户发起金额为{$order['first_refundprice']},确认退款金额为{$price}元,等待退款到账",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}else{
OrderLog::log($order['id'],"活动订单售后已处理,用户发起金额为{$order['first_refundprice']},确认退款金额为{$price}元,等待退款到账",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}
//调用订单事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$user_id,"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_shop_confirm_after', $data);
//执行退款
//执行退款
self::orderRefund($order['order_no'],$price,$oper_type,$oper_id);
break;
case 'no':
//拒绝
//更新
//更新售后为机构驳回结单状态
$order = self::updateShopConfirmationFailOrder($order,$reject_reason);
//插入订单日志
if(!$user_id ||$order["user_id"] !=$user_id ){
OrderLog::log($order['id'],"[系统操作]活动订单售后驳回,订单挂起,等待官方处理",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}else{
OrderLog::log($order['id'],"活动订单售后驳回,订单挂起,等待官方处理",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}
//调用订单取消事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$user_id,"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_shop_reject_after', $data);
break;
default:
throw new \Exception("参数错误!");
}
//执行课时数更新
$res1 = order::statisticsAndUpdateClassesNumber($order);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage());
}
return $res1;
}
/** 检测售后订单超时未处理自动挂起定时任务
* @param $id 售后订单id非必填不填则检测所有超时未处理的
* @param $trans
* @return true
* @throws \Exception
*/
public function timeoutSuspension($id=0,$trans=false){
//去超时时间戳配置
$timeout = config('site.activity_end_sales');
//当前时间
$time = time();
//延期时间过了当前时间的时间点(用时间戳相减特性)
$delay_time = $time - $timeout;
//初始查询model
$as = $this->getWithAlisaName();
$model = self::with(["detail"])
->where("{$as}.server_status",'3')
->where("{$as}.suspend_status",'0')
->where("{$as}.status",'4');
if($id){
$model = $model->where("{$as}.id",$id);
}
//查询条件2过了活动结束时间加延期时间的订单
$model = $model->where("detail.end_time","<",$delay_time);
$list = $model->select();
if($trans){
self::beginTrans();
}
$res = true;
try{
//遍历订单,执行挂起操作
foreach ($list as $row){
//执行挂起操作
$row["status"] = "7";
$row["server_status"] = "6";
$row["suspend_status"] = "2";
$row["auth_status"] = 2;
$row["auth_time"] = time();
$row->save();
//写订单日志
OrderLog::log($row['id'],"活动订单售后超时自动驳回,订单挂起,等待官方处理",'admin', 0);
//调用挂起事件
$data = ['order' => self::where("id",$row['id'])->find(),"user_id"=>0,"oper_type"=>"admin","oper_id"=>0];
\think\Hook::listen('activity_order_timeout_reject_after', $data);
}
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $res;
}
/**总后台最后处理售后挂起的订单
* @param $order_no
* @param int $user_id
* @param string $status yes同意 no拒绝即订单按完成算
* @param string $price 商定价格
* @param bool $check
* @param bool $trans
* @return bool
* @throws \Exception
*/
public function adminConfirmation($order_no,$status,$price,$user_id=0,$check=false,$oper_type='user',$oper_id=0,$trans=false){
//得到机构售后提交确认订单
$order = self::getHaveShopConfirmationOrder($order_no);
if($check){
//用户操作权限检测
Order::checkOptionAuth($order['id'],$user_id ?: $oper_id,$oper_type,false,false,true);
}
$classesorder = $order;
if(!$classesorder)throw new \Exception("订单不存在!");
switch ($status){
case 'yes':
//同意
//验证价格正确性
//金额必须小于等于剩余未退全额
$price = floatval($price);
if($price > $classesorder['sub_refundprice']){
throw new \Exception("价格必须小于等于剩余应退全额!");
}
//数据修正大于剩余未退全额 则等于剩余未退
$price = $price > $classesorder['sub_refundprice'] ? $classesorder['sub_refundprice'] : $price;
if($price<=0)throw new \Exception("同意退款必须大于0");
//如果非全额退款,剩余金额必须大于订单支付时的手续费
if($price < $classesorder['sub_refundprice']){
$sub_price = bcsub($classesorder['sub_refundprice'] , $price,2);
if($sub_price <= $classesorder['fee_price']){
throw new \Exception("如果未退全款,剩余未退金额必须大于订单支付时的手续费{$classesorder['fee_price']}");
}
}
break;
case 'no':
//拒绝
break;
default:
throw new \Exception("参数错误!");
}
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//事务逻辑
//更新售后为取消状态
switch ($status){
case 'yes':
//同意
//更新订单状态为同意
$order = self::updateShopConfirmationOrder($order,$price);
//插入订单日志
if(!$user_id ||$order["user_id"] !=$user_id ){
OrderLog::log($order['id'],"[系统操作]活动订单售后最终处理结果:用户发起金额为{$order['first_refundprice']},确认退款金额为{$price}元,等待退款到账",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}else{
OrderLog::log($order['id'],"活动订单售后最终处理结果:用户发起金额为{$order['first_refundprice']},确认退款金额为{$price}元,等待退款到账",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}
//调用订单事件
$data = ['order' => self::where("id",$order['id'])->find(),"user_id"=>$user_id,"oper_type"=>$oper_type,"oper_id"=>$oper_id];
\think\Hook::listen('activity_order_shop_confirm_after', $data);
//执行退款
//执行退款
self::orderRefund($order['order_no'],$price,$oper_type,$oper_id);
break;
case 'no':
//拒绝
//插入订单日志
if(!$user_id ||$order["user_id"] !=$user_id ){
OrderLog::log($order['id'],"[系统操作]活动订单售后最终判定为不进行售后,将不退款,按订单完成处理",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}else{
OrderLog::log($order['id'],"活动订单售后最终判定为不进行售后,将不退款,按订单完成处理",$oper_type ?: 'user', $oper_id ?: $order['user_id']);
}
//调用订单完成
//执行订单完成的更新逻辑
self::updateFinish($order);
break;
default:
throw new \Exception("参数错误!");
}
//执行课时数更新
$res1 = order::statisticsAndUpdateClassesNumber($order);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage());
}
return $res1;
}
}