validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); $point = $params['point']; $people_num = !empty($params['people_num']) ? $params['people_num'] : 1; $luggage_num = !empty($params['luggage_num']) ? $params['luggage_num'] : 0; $is_back = !empty($params['is_back']) ? $params['is_back'] : 0; $is_exclusive = !empty($params['is_exclusive']) ? $params['is_exclusive'] : 0; $appointment_time = !empty($params['appointment_time']) ? strtotime($params['appointment_time']) : 0; $appointment_time = $params['type'] == 30 && empty($appointment_time) ? time() : $appointment_time;// 如果是顺风车 且未选择时间,则默认为当前时间 // 计算公里数 $distanceBusiness = new DistanceBusiness(); if (!$distanceBusiness->computeRoutes($point, $appointment_time)) { return AppResult::error($distanceBusiness->getMessage() ?? '计算距离错误'); } $mapData = $distanceBusiness->getData(); // 获取计算距离 $distance = $mapData['distance'];// 距离m $duration = $mapData['duration'];// 时长s // 计算对应订单类型的价格 $driverTypeBusiness = new DriverTypeBusiness(); if (!$driverTypeBusiness->calculate((int)$params['type'], $distance, $duration, $point, $appointment_time, $is_back, $is_exclusive)) { return AppResult::error($driverTypeBusiness->getMessage() ?? '计算失败'); } $res = $driverTypeBusiness->getData(); if ($params['type'] == 50) { $total_amount = (string)round((float)$res['total_amount']);// 四舍五入 $rmb_total_amount = (string)round((float)bcmul($total_amount, site('dollar_to_rmb'), 2));// 四舍五入 // 跑腿订单 $data = [ 'errand' => [ 'total_amount' => $total_amount, 'rmb_total_amount' => $rmb_total_amount ] ]; } else { $airport_fee_min_price = site('airport_fee_min_price');// 接送机最低消费 $carSeat = new CarSeatModel(); $list = $carSeat->getList(orderBy: ['weight' => 'desc']); foreach ($list as $key => $val) { $basics_amount = bcsub(bcmul((string)$res['basics_amount'], (string)$val['subsidy_multiple'], 2),(string)$res['basics_amount'],2);// 计算车型补贴:公里数价格 * 补贴比例 $total_amount = bcadd((string)$res['total_amount'], $basics_amount, 2); // 预约费 $platform_subscribe_price = (string)($total_amount >= site('platform_subscribe_min') ? $val['platform_subscribe_price'] : '0'); $total_amount = (string)round((float)bcadd($total_amount,$platform_subscribe_price,2));// 美元 四舍五入 if ($params['type'] == 20 || ($params['type'] == 10 && $res['is_at_airport'] == 1)){ $total_amount = max($total_amount,$airport_fee_min_price);// 接送机最低消费 } $rmb_total_amount = (string)round((float)bcmul($total_amount, site('dollar_to_rmb'), 2));// 人民币 四舍五入 $list[$key]['image'] = cdn_url($val['image']); $list[$key]['total_amount'] = $total_amount; $list[$key]['rmb_total_amount'] = $rmb_total_amount; $list[$key]['is_show'] = $val['people_num'] >= $people_num && $val['luggage_num'] >= $luggage_num ? 1 : 0; } // 往返提示说明 $come_explain = ''; if ($is_back == 1){ $come_explain = $params['type'] == 10 ? site('wf_taxi_explain') : site('wf_airport_explain'); } $data = [ 'distance' => bcdiv((string)$distance,'1000'), 'come_explain' => $come_explain, 'car_seat_list' => $list, 'tailwind' => [ [ 'image' => cdn_url('/assets/img/shun_che1.png'), 'total_amount' => (string)round((float)$res['total_amount']), 'rmb_total_amount' => (string)round((float)bcmul($res['total_amount'], site('dollar_to_rmb'), 2)), 'name' => '拼车', 'is_exclusive' => 0, ], [ 'image' => cdn_url('/assets/img/shun_che2.png'), 'total_amount' => (string)round((float)bcadd($res['total_amount'], site('tailwind_exclusive'), 2)), 'rmb_total_amount' => (string)round((float)bcmul(bcadd($res['total_amount'], site('tailwind_exclusive'), 2),site('dollar_to_rmb'), 2)), 'name' => '独享', 'is_exclusive' => 1, ] ], 'substitute' => [ 'total_amount' => (string)round((float)$res['total_amount']), 'rmb_total_amount' => (string)round((float)bcmul($res['total_amount'], site('dollar_to_rmb'), 2)), 'image' => cdn_url('/assets/img/dai_toup1.png'), ] ]; } // 汇率 $data['dollar_to_rmb'] = site('dollar_to_rmb'); if ($appointment_time == 0){ // 如果有实时订单则提示是否继续接单 $model = new OrderModel(); if ($model->getDetail(params: ['user_id' => $user['id'],'status_in' => [10,20,30],'appointment_time_zero' => 0])){ return AppResult::response_fast(2,"您有一条正在进行订单,确定要继续下单吗?", $data); } } return AppResult::success('success', $data); } /** * 提交订单(快车 接送机) * @param SubmitRequest $request * @return string */ public function submit(SubmitRequest $request) { $params = $request->validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); // 如果是会员 则可设置一口价 if ($user['is_vip'] != 1 && !empty($params['pay_amount'])){ return AppResult::error('暂无设置一口价权限'); } if ($user['is_vip'] == 1 && !empty($params['pay_amount']) && !in_array($params['type'], [10, 20])){ return AppResult::error('仅支持快车或接送机设置一口价'); } $point = $params['point']; $people_num = !empty($params['people_num']) ? $params['people_num'] : 1;// 乘车人数 $luggage_num = !empty($params['luggage_num']) ? $params['luggage_num'] : 0;// 行李数 $is_back = !empty($params['is_back']) ? $params['is_back'] : 0;// 是否往返 $is_exclusive = !empty($params['is_exclusive']) ? $params['is_exclusive'] : 0;// 是否独享 $appointment_time = !empty($params['appointment_time']) ? strtotime($params['appointment_time']) : 0;// 出发时间 $appointment_time = $params['type'] == 30 && empty($appointment_time) ? time() : $appointment_time;// 如果是顺风车 且未选择时间,则默认为当前时间 // 计算公里数 $distanceBusiness = new DistanceBusiness(); if (!$distanceBusiness->computeRoutes($point, $appointment_time)) { return AppResult::error($distanceBusiness->getMessage() ?? '计算距离错误'); } $mapData = $distanceBusiness->getData(); // 获取计算距离 $distance = $mapData['distance'];// 距离m $duration = $mapData['duration'];// 时长s // 如果是会员 且是快车 或 接送机 则可设置一口价 if ($user['is_vip'] == 1 && !empty($params['pay_amount']) && in_array($params['type'], [10, 20])){ $carSeat = new CarSeatModel(); $info = $carSeat->getDetail(params: ['id' => $params['car_seat_id']]); if (!($info['people_num'] >= $people_num && $info['luggage_num'] >= $luggage_num)) { return AppResult::error('当前车型不支持选择的人数行李数'); } $pay_amount = $total_amount = $params['pay_amount']; // 预约费 vip预约费已经包含在 $platform_subscribe_price = (string)($total_amount >= site('platform_subscribe_min') ? $info['platform_subscribe_price'] : '0'); }else{ // 计算对应订单类型的价格 $driverTypeBusiness = new DriverTypeBusiness(); if (!$driverTypeBusiness->calculate((int)$params['type'], $distance, $duration, $point, $appointment_time, $is_back, $is_exclusive)) { return AppResult::error($driverTypeBusiness->getMessage() ?? '计算失败'); } $res = $driverTypeBusiness->getData(); // 如果是接送机 快车 则根据车型计算价格 if (in_array($params['type'], [10, 20])) { $carSeat = new CarSeatModel(); $info = $carSeat->getDetail(params: ['id' => $params['car_seat_id']]); if (!($info['people_num'] >= $people_num && $info['luggage_num'] >= $luggage_num)) { return AppResult::error('当前车型不支持选择的人数行李数'); } $basics_amount = bcsub(bcmul((string)$res['basics_amount'], (string)$info['subsidy_multiple'], 2),(string)$res['basics_amount'],2);// 计算车型补贴:公里数价格 * 补贴比例 $total_amount = bcadd((string)$res['total_amount'], $basics_amount, 2); // 预约费 $platform_subscribe_price = (string)($total_amount >= site('platform_subscribe_min') ? $info['platform_subscribe_price'] : '0'); $total_amount = bcadd($total_amount,$platform_subscribe_price,2); if ($params['type'] == 20 || ($params['type'] == 10 && $res['is_at_airport'] == 1)){ $airport_fee_min_price = site('airport_fee_min_price');// 接送机最低消费 $total_amount = max($total_amount,$airport_fee_min_price); } } // 如果是 代驾 顺风车 跑腿 则无需单独增加价格 if (in_array($params['type'], [30, 40, 50])) { $total_amount = $res['total_amount']; } if (!isset($total_amount)) { return AppResult::error('操作异常'); } $pay_amount = $total_amount = (string)round((float)$total_amount);// 支付金额 四舍五入 // 优惠券核销 if (!empty($params['coupon_id'])){ $order_coupons = (new UserCouponModel())->getOrderCoupon($user['id'],(int)$params['type'],(int)$params['coupon_id'],$total_amount); if ($total_amount >= $order_coupons['min_money']){ $pay_amount = bcsub($total_amount,$order_coupons['money'],2); $discount_amount = $order_coupons['money']; } } } // 在线支付需校验金额 if ($params['pay_type'] == 1) { $money = UserWalletModel::getOne($user['id'], 'money'); if ($money < $pay_amount) { return AppResult::error('余额不足'); } } $data = [ 'type' => $params['type'], 'flight_type' => $params['flight_type'] ?? 0, 'distance' => (int)$distance, 'duration' => (int)$duration, 'total_amount' => $total_amount, 'dollar_to_rmb' => site('dollar_to_rmb'), 'pay_amount' => $pay_amount, 'discount_amount' => $discount_amount ?? 0, 'pay_type' => $params['pay_type'], 'is_pay' => $params['pay_type'] == 1 ? 1 : 0, 'coupon_id' => $params['coupon_id'] ?? 0, 'car_seat_id' => $params['car_seat_id'] ?? 0, 'name' => $params['name'] ?? $user['nickname'], 'phone' => $params['phone'] ?? "{$user['area_code']} {$user['mobile']}", 'people_num' => $people_num, 'luggage_num' => $luggage_num, 'start_lng' => $point[0]['lng'], 'start_lat' => $point[0]['lat'], 'end_lng' => end($point)['lng'], 'end_lat' => end($point)['lat'], 'is_back' => $is_back, 'is_exclusive' => $is_exclusive, 'is_child' => $params['is_child'] ?? 0, 'appointment_time' => $appointment_time, 'flight_no' => $params['flight_no'] ?? '', 'basics_amount' => $res['basics_amount'] ?? '0.00', 'city_subsidy_price' => $res['city_subsidy_price'] ?? '0.00', 'peak_price' => $res['peak_price'] ?? '0.00', 'night_price' => $res['night_price'] ?? '0.00', 'exclusive_price' => $res['exclusive_price'] ?? '0.00', 'platform_subscribe_price' => $platform_subscribe_price ?? '0.00', 'remark' => $params['remark'] ?? '', 'freight_name' => $params['freight_name'] ?? '', 'image' => $params['image'] ?? '', ]; Db::beginTransaction(); // 在线支付需要提前扣费 if ($params['pay_type'] == 1) { $type_name = DriverTypeBusiness::type[$params['type']] ?? ''; $wallet = new UserWalletModel(); if (!$wallet->change($user['id'], (float)$data['pay_amount'], "{$type_name}订单消费")) { Db::rollBack(); return AppResult::error($wallet->getMessage()); } } // 使用优惠券 if (!empty($params['coupon_id'])) { $coupon = UserCouponModel::query() ->where('id',$params['coupon_id']) ->where('type',$params['type']) ->where('is_use',0) ->where('status',1) ->where('valid_at', '>', time()) ->update(['is_use' => 1, 'use_at' => time()]); if (!$coupon) { Db::rollBack(); return AppResult::error('优惠券核销失败,请校验优惠券'); } } // 增加用户订单数 UserModel::query()->where('id', $user['id'])->increment('order_num'); // 创建订单 $model = new OrderModel(); if (!$model->createOrder($user['id'], $data, $point)) { Db::rollBack(); return AppResult::error($model->getMessage()); } Db::commit(); // 加入提醒司机接单队列 if (!empty($model->getData()['order_no'])){ $this->service->pushOrder([ 'order_no' => $model->getData()['order_no'] ]); } // 加入超时订单取消队列 if (in_array($params['type'],[10,20,30,40,50])){ $delay = $appointment_time > 0 ? 600 : 300;// 延迟时间 单位秒 if ($params['type'] == 30){ $delay = $appointment_time - time(); $delay = max($delay, 600); } $this->service->cancelOrder([ 'order_no' => $model->getData()['order_no'] ],$delay); } return AppResult::success($model->getMessage(), $model->getData()); } /** * 订单列表 * @param ListRequest $request * @return string */ public function list(ListRequest $request) { $params = $request->validated(); $user = AuthUser::getInstance()->get(); $params['user_id'] = $user['id']; // 列表筛选 switch ($params['status_type']) { case 1: $params['status_in'] = [10, 20]; $params['appointment_time_not'] = 1; break; case 2: $params['status_in'] = [10, 20, 30]; break; case 3: $params['status_in'] = [40]; } $model = new OrderModel(); $model->setSelect([ 'id', 'user_id', 'driver_id', 'type', 'order_no', 'driver_order_id', 'total_amount', 'pay_amount', 'appointment_time', 'create_time', 'driver_status', 'status' ]); $list = $model->getList( params : $params, orderBy: ['id' => 'desc'], with : [ 'points' => function ($query) { $query->select('id', 'order_id', 'type', 'lng', 'lat', 'address', 'postal_code', 'is_in_city', 'is_at_airport')->where('status', 1)->orderBy('id', 'asc'); } ] ); foreach ($list as $key => $val) { $list[$key]['start_time'] = date('Y-m-d H:i', !empty($val['appointment_time']) ? $val['appointment_time'] : $val['create_time']); $list[$key]['create_time'] = strtotime($list[$key]['start_time']); } return AppResult::success(result: $list); } /** * 订单详情 * @param DetailRequest $request * @return string */ public function detail(DetailRequest $request) { $params = $request->validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); $model = new OrderModel(); $model->setSelect([ 'id', 'user_id', 'driver_id', 'type', 'flight_type', 'order_no', 'driver_order_id', 'total_amount', 'pay_amount', 'is_pay', 'raise_amount', 'pay_type', 'car_seat_id', 'name', 'phone', 'people_num', 'luggage_num', 'remark', 'distance', 'duration', 'is_back', 'is_exclusive', 'is_child', 'appointment_time', 'flight_no', 'car_no', 'car_color', 'car_brand', 'take_time', 'driver_status', 'status', 'create_time', 'start_driver_time', 'end_driver_time' ]); $info = $model->getDetail( params: ['order_no' => $params['order_no'], 'user_id' => $user['id']], with : [ 'driver' => function ($query) { $query->select('id', 'name', 'area_code', 'mobile', 'avatar'); }, 'points' => function ($query) { $query->select('id', 'order_id', 'type', 'lng', 'lat', 'address', 'postal_code', 'is_in_city', 'is_at_airport')->where('status', 1)->orderBy('id', 'asc'); } ] ); if (!$info) { return AppResult::error('订单不存在'); } $car_img = DriverCarModel::query()->where('driver_id',$info['driver']['id'] ?? 0)->value('car_img'); $info['end_driver_time'] = $info['driver_status'] == 2 ? time_hour(!empty($info['end_driver_time']) ? $info['end_driver_time'] : ($info['end_driver_time'] + $info['duration'])) : 0; $info['driver_name'] = $info['driver']['name'] ?? ''; $info['driver_avatar'] = $car_img ?? ''; $info['driver_mobile'] = ($info['driver']['area_code'] ?? '') . ' ' . ($info['driver']['mobile'] ?? ''); $info['service_telephone'] = site('service_telephone');// 客服电话 unset($info['driver']); return AppResult::success('success', $info); } /** * 加价 * @param AddMoneyRequest $request * @return string */ public function add_money(AddMoneyRequest $request) { $params = $request->validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); $model = new OrderModel(); $info = $model->getDetail( params: ['order_no' => $params['order_no'], 'user_id' => $user['id'], 'status' => 10], ); if (!$info) { return AppResult::error('订单不存在或已被接单'); } // 在线支付需校验金额 if ($info['pay_type'] == 1) { $money = UserWalletModel::getOne($user['id'], 'money'); if ($money < $params['amount']) { return AppResult::error('余额不足'); } } Db::beginTransaction(); // 在线支付需要提前扣费 if ($info['pay_type'] == 1) { $type_name = DriverTypeBusiness::type[$info['type']] ?? ''; $wallet = new UserWalletModel(); if (!$wallet->change($user['id'], (float)$params['amount'], "{$type_name}订单加价消费")) { Db::rollBack(); return AppResult::error($wallet->getMessage()); } } if (!$model->addMoney($info['id'], (float)$params['amount'])) { Db::rollBack(); return AppResult::error($model->getMessage()); } Db::commit(); return AppResult::success($model->getMessage()); } /** * 取消订单 * @param DetailRequest $request * @return string */ public function cancel(CancelRequest $request) { $params = $request->validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); Db::beginTransaction(); $model = new OrderModel(); if (!$model->cancel(order_no: $params['order_no'], user_id: $user['id'],reason: $params['reason'])) { Db::rollBack(); return AppResult::error($model->getMessage()); } Db::commit(); return AppResult::success($model->getMessage()); } /** * 司机发布顺风车 * @param TailwindDriverRequest $request * @return string */ public function tailwind_driver(TailwindDriverRequest $request) { $params = $request->validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); $start_lng = $params['point'][0]['lng'] ?? ''; $start_lat = $params['point'][0]['lat'] ?? ''; $end_lng = $params['point'][1]['lng'] ?? '0.00'; $end_lat = $params['point'][1]['lat'] ?? '0.00'; unset($params['point']); $params['status'] = 1; $params['start_time_gt'] = time(); $model = new OrderDriverModel(); $model->setSelect([ 'id', 'driver_id', 'car_no', 'remark', 'start_time', 'people_num', 'people_at_num', 'start_address', 'start_lng', 'start_lat', 'end_address', 'end_lng', 'end_lat', 'status', 'create_time', Db::raw("(st_distance(point ({$start_lng}, {$start_lat}),point(start_lng,start_lat))*111195) as start_distance"), Db::raw("(st_distance(point ({$end_lng}, {$end_lat}),point(end_lng,end_lat))*111195) as end_distance"), ]); $list = $model->getList( params : $params, orderBy: ['start_time'=>'asc','start_distance' => 'asc', 'end_distance' => 'asc'], with : [ 'order' => function ($query) use ($user) { $query->select('id', 'driver_order_id', 'user_id', 'driver_id', 'status')->where('user_id', $user['id'] ?? 0)->whereIn('status', [10, 20, 30, 40]); }, 'driver' => function ($query) { $query->select('id', 'name', 'avatar'); }, 'car' => function ($query) { $query->select('driver_id', 'car_color', 'car_brand'); } ] ); foreach ($list as $key => $val) { // 起点终点总距离差 $total_distance = bcadd((string)$val['start_distance'], (string)($val['end_distance'] ?? 0), 2); $list[$key]['total_distance'] = $total_distance; // 起点距离 $stat_distance = 0; if (isset($val['start_distance'])) { if ($val['start_distance'] >= 1000) { $stat_distance = round(($val['start_distance'] / 1000), 2) . 'km'; } else { $stat_distance = round($val['start_distance'], 1) . 'm'; } } $list[$key]['start_distance'] = $stat_distance; // 终点距离 $end_distance = 0; if (isset($val['end_distance'])) { if ($val['end_distance'] >= 1000) { $end_distance = round(($val['end_distance'] / 1000), 2) . 'km'; } else { $end_distance = round($val['end_distance'], 1) . 'm'; } } $list[$key]['end_distance'] = $end_distance; $list[$key]['start_time'] = time_hour($val['start_time']); $list[$key]['is_subscribe'] = !empty($val['order']) ? 1 : 0; $list[$key]['car_color'] = $val['car']['car_color'] ?? ''; $list[$key]['car_brand'] = $val['car']['car_brand'] ?? ''; $list[$key]['driver_name'] = $val['driver']['name'] ?? ''; $list[$key]['driver_avatar'] = !empty($val['driver']['avatar']) ? cdn_url($val['driver']['avatar']) : ''; unset($list[$key]['car'], $list[$key]['driver']); } return AppResult::success('success', $list); } /** * 司机发布顺风车详情 * @param TailwindDriverInfoRequest $request * @return string */ public function tailwind_driver_info(TailwindDriverInfoRequest $request) { $params = $request->validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); $params['status'] = 1; $model = new OrderDriverModel(); $model->setSelect([ 'id', 'driver_id', 'car_no', 'remark', 'start_time', 'people_num', 'people_at_num', 'start_address', 'start_lng', 'start_lat', 'end_address', 'end_lng', 'end_lat', 'status', 'create_time' ]); $info = $model->getDetail( params : $params, with : [ 'order' => function ($query) use ($user) { $query->select('id', 'driver_order_id', 'user_id', 'driver_id', 'status')->where('user_id', $user['id'] ?? 0)->whereIn('status', [10, 20, 30, 40]); }, 'driver' => function ($query) { $query->select('id', 'name', 'avatar'); }, 'car' => function ($query) { $query->select('driver_id', 'car_color', 'car_brand'); } ] ); $info['start_time'] = time_hour($info['start_time']); $info['is_subscribe'] = !empty($info['order']) ? 1 : 0; $info['car_color'] = $info['car']['car_color'] ?? ''; $info['car_brand'] = $info['car']['car_brand'] ?? ''; $info['driver_name'] = $info['driver']['name'] ?? ''; $info['driver_avatar'] = !empty($info['driver']['avatar']) ? cdn_url($info['driver']['avatar']) : ''; unset($info['car'], $info['driver']); return AppResult::success('success', $info); } /** * 邀请司机接单 * @param TailwindInviteRequest $request * @return string */ public function tailwind_invite(TailwindInviteRequest $request) { $params = $request->validated();// 获取校验通过的参数 $user = AuthUser::getInstance()->get(); $model = new OrderModel(); $info = $model->getDetail( params: ['order_no' => $params['order_no'], 'user_id' => $user['id'], 'status' => 10], ); if (!$info) { return AppResult::error('订单不存在或已被接单'); } $driver_order = OrderDriverModel::query()->where('id', $params['driver_order_id'])->where('status',1)->first(); if (!$driver_order){ return AppResult::error('当前拼车路线不存在或行程已出发'); } if (!$model->tailwind_invite($info['id'], $params['driver_order_id'])) { return AppResult::error($model->getMessage()); } // 发送系统消息 DriverMessageModel::add([ 'driver_id' => $driver_order['driver_id'], 'type' => 1, 'name' => "顺风车有新订单", 'content' => "用户邀请您同行,请前往顺风车行程中查看详情", 'value' => $params['order_no'] ]); return AppResult::success($model->getMessage()); } }