agentService = $agentService; $this->projectService = $projectService; } /** * 订单初始化 * * 初始化订单信息,包括用户钱包、技师信息、项目信息、地址信息和订单金额等 * * @param int $userId 用户ID * @param array $data 订单数据 * @return array 返回初始化的订单信息 * * @throws \Exception 初始化失败时抛出异常 */ public function initialize(int $userId, array $data): array { try { // 参数验证 abort_if(empty($data['project_id']), 400, '项目ID不能为空'); abort_if(empty($data['coach_id']), 400, '技师ID不能为空'); return DB::transaction(function () use ($userId, $data) { $user = MemberUser::find($userId); abort_if(! $user || $user->state != UserStatus::OPEN->value, 400, '用户状态异常'); // 查询用户钱包 $wallet = $user->wallet; // 查询默认地址 $address = $user->address; $areaCode = $address ? $address->area_code : ($data['area_code'] ?? null); abort_if(empty($areaCode), 400, '区域编码不能为空'); // 查询技师数据 $coach = $this->validateCoach($data['coach_id']); // 查询技师可服务时间段 $availableTimeSlots = app(CoachService::class)->getSchedule($coach->id); // 获取项目详情 $project = $this->projectService->getProjectDetail($data['project_id'], $areaCode); abort_if(! $project, 400, '项目不存在'); // 计算订单金额 $amounts = $this->calculateOrderAmount( $userId, $address?->id ?? 0, $data['coach_id'], $data['project_id'], $project->agent_id, false, $data['latitude'], $data['longitude'] ); return [ 'wallet' => $wallet, 'coach' => $coach, 'project' => $project, 'address' => $address, 'amounts' => $amounts, 'availableTimeSlots' => $availableTimeSlots, ]; }); } catch (Exception $e) { Log::error('订单初始化失败', [ 'userId' => $userId, 'data' => $data, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); throw $e; } } /** * 创建订单 */ public function createOrder(int $userId, array $data): array { // TODO:检测技师可服务时间段 return DB::transaction(function () use ($userId, $data) { // 1. 参数校验 $user = MemberUser::where('id', $userId) ->where('state', UserStatus::OPEN->value()) ->firstOrFail(); $project = Project::where('id', $data['project_id']) ->where('state', ProjectStatus::OPEN->value()) ->firstOrFail(); // 2. 订单类型判断 $orderType = $data['order_type']; // 关键操作:验证必要参数 abort_if(empty($data['project_id']), 400, '项目ID不能为空'); // 上门订单必须指定技师和地址 abort_if($orderType == OrderType::VISIT->value && empty($data['coach_id']), 400, '技师ID不能为空'); abort_if($orderType == OrderType::VISIT->value && empty($data['address_id']), 400, '地址ID不能为空'); // 加钟订单必须指定原订单 abort_if($orderType == OrderType::OVERTIME->value && empty($data['order_id']), 400, '原订单ID不能为空'); // 到店订单必须指定店铺 abort_if($orderType == OrderType::SHOP->value && empty($data['shop_id']), 400, '店铺ID不能为空'); // 应急订单必须指定地址 abort_if($orderType == OrderType::EMERGENCY->value && empty($data['address_id']), 400, '地址ID不能为空'); // 3. 验证技师 // 4. 根据订单类型处理 // 上门订单 if ($orderType == OrderType::VISIT->value) { $this->validateCoach($data['coach_id']); } // 加钟订单 if ($orderType == OrderType::OVERTIME->value) { $originalOrder = $this->getOriginalOrder($user, $data['order_id']); $data['address_id'] = $originalOrder->address_id; $this->validateCoach($originalOrder->coach_id); abort_if(! in_array($originalOrder->state, [OrderStatus::SERVING->value, OrderStatus::FINISHED->value]), 400, '原订单状态不允许加钟'); $data = $this->prepareAddTimeData($originalOrder, $data); } $address = $user->addresses() ->where('id', $data['address_id']) ->firstOrFail(); // 5. 计算订单金额 $data['use_balance'] = $data['use_balance'] ?? false; $amounts = $this->calculateOrderAmount( $userId, $address->id, $data['coach_id'], $data['project_id'], $project?->agent_id, $data['use_balance'] ); // 6. 验证金额和余额 abort_if($amounts['total_amount'] <= 0, 400, '订单金额异常'); if ($data['payment_type'] == PaymentMethod::BALANCE->value) { $wallet = $user->wallet; abort_if($wallet->available_balance < $amounts['balance_amount'], 400, '可用余额不足'); } // 项目服务时长(分钟) $data['duration'] = $project->duration; // 7. 创建订单记录 $order = $this->createOrderRecord($userId, $data, $orderType, $address, $data['payment_type'], (object) $amounts); // 8. 余额支付处理 if ($order->payment_type == PaymentMethod::BALANCE->value && $orderType != OrderType::GRAB->value) { $this->handleBalancePayment($user, $order, $orderType); } return [ 'order_id' => $order->id, 'payment_type' => $order->payment_type, ]; }); } // 提取方法:验证技师 public function validateCoach(int $coachId): CoachUser { return CoachUser::where('id', $coachId) ->where('state', TechnicianStatus::ACTIVE->value) ->whereHas('info', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value)) ->whereHas('qual', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value)) ->whereHas('real', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value)) ->firstOrFail(); } // 提取方法:获取原始订单 private function getOriginalOrder($user, $orderId): Order { $originalOrder = $user->orders->where('id', $orderId) ->whereIn('state', [OrderStatus::SERVING->value, OrderStatus::FINISHED->value]) ->firstOrFail(); return $originalOrder; } // 提取方法:准备加钟订单数据 private function prepareAddTimeData($originalOrder, $data): array { if ($originalOrder->state == OrderStatus::SERVING->value) { $startTime = now(); } else { $startTime = now(); } return [ ...$data, 'order_id' => $data['order_id'], 'address_id' => $originalOrder->address_id, 'service_time' => $startTime, 'coach_id' => $originalOrder->coach_id, ]; } // 提取方法:创建订单记录 private function createOrderRecord($userId, $data, $orderType, $address, $payment_type, object $amounts): Order { // 计算服务开始和结束时间 $serviceStartTime = Carbon::parse($data['service_time']); $serviceEndTime = $serviceStartTime->copy()->addMinutes($data['duration']); // TODO: 优化结构 $order = new Order; $order->user_id = $userId; $order->order_no = 'O'.Carbon::now()->format('YmdHis').rand(1000, 9999); $order->project_id = $data['project_id']; $order->coach_id = $data['coach_id']; $order->type = $orderType; $order->state = OrderStatus::CREATED->value; $order->source = OrderSource::PLATFORM->value; $order->total_amount = $amounts->total_amount; $order->balance_amount = $amounts->balance_amount; $order->pay_amount = $amounts->pay_amount; $order->project_amount = $amounts->project_amount; $order->traffic_amount = $orderType == OrderType::OVERTIME->value ? 0 : $amounts->delivery_fee; $order->payment_type = ($data['use_balance'] && $amounts->pay_amount == 0) ? PaymentMethod::BALANCE->value : $payment_type; $order->service_time = $data['service_time']; $order->address_id = $data['address_id']; $order->longitude = $address->longitude; $order->latitude = $address->latitude; $order->location = $address->location; $order->address = $address->address; $order->area_code = $address->area_code; $order->service_start_time = $serviceStartTime; $order->service_end_time = $serviceEndTime; $order->save(); // 创建订单记录 OrderRecord::create([ 'order_id' => $order->id, 'object_id' => $userId, 'object_type' => MemberUser::class, 'state' => OrderStatus::CREATED->value, 'remark' => $orderType == OrderType::OVERTIME->value ? '加钟订单' : '创建订单', ]); if ($orderType == OrderType::GRAB->value) { // 将订单地址经纬度存入redis的geo类型 Redis::geoadd( 'order_locations', $order->longitude, $order->latitude, 'order:'.$order->id ); } return $order; } // 提取方法:处理余额支付 private function handleBalancePayment($user, $order, $orderType): void { $order->state = $orderType == OrderType::VISIT->value || $orderType == OrderType::GRAB->value ? OrderStatus::PAID->value : OrderStatus::SERVING->value; $order->save(); OrderRecord::create([ 'order_id' => $order->id, 'object_id' => $user->id, 'object_type' => MemberUser::class, 'state' => OrderRecordStatus::PAID->value, 'remark' => '余额支付', ]); $user->wallet->decrement('total_balance', $order->balance_amount); $user->wallet->decrement('available_balance', $order->balance_amount); } /** * 取消订单 */ public function cancelOrder(int $userId, int $orderId): array { return DB::transaction(function () use ($userId, $orderId) { try { // 1. 验证用户和订单 $order = $this->validateOrderForCancel($userId, $orderId); abort_if($order->state == OrderStatus::CANCELLED->value, 400, '订单已取消'); // 2. 处理退款 if (in_array($order->state, [OrderStatus::PAID->value, OrderStatus::ACCEPTED->value, OrderStatus::DEPARTED->value])) { $this->handleCancelRefund($order); } // 3. 完成订单取消 $this->completeCancel($order, $userId); // 4. 通知技师 if ($order->coach_id) { // event(new OrderCancelledEvent($order)); } return ['message' => '订单已取消']; } catch (Exception $e) { $this->logCancelOrderError($e, $userId, $orderId); throw $e; } }); } /** * 验证订单取消条件 */ private function validateOrderForCancel(int $userId, int $orderId): Order { // 复用之前的用户验证逻辑 $user = MemberUser::where('id', $userId) ->where('state', UserStatus::OPEN->value) ->firstOrFail(); // 验证订单状态 $order = Order::where('user_id', $userId) ->where('id', $orderId) ->whereIn('state', [OrderStatus::CREATED->value, OrderStatus::ASSIGNED->value, OrderStatus::PAID->value, OrderStatus::ACCEPTED->value, OrderStatus::DEPARTED->value]) ->lockForUpdate() ->firstOrFail(); return $order; } /** * 处理订单取消退款 */ private function handleCancelRefund(Order $order): void { $user = $order->user; switch ($order->state) { case OrderStatus::ACCEPTED->value: // 已接单 // 扣除20%费用 $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.2; $this->handleRefund($user, $order, $deductAmount, false); break; case OrderStatus::DEPARTED->value: // 已出发 // 扣除50%费用并扣除路费 $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.5; $this->handleRefund($user, $order, $deductAmount, true); break; case OrderStatus::PAID->value: // 已支付 // 返还所有金额 $deductAmount = 0; $this->handleRefund($user, $order, $deductAmount, false); break; case OrderStatus::CREATED->value: // 待支付状态直接取消,无需退款 break; default: abort(400, '当前订单状态不允许取消'); } } /** * 完成订单取消 */ private function completeCancel(Order $order, int $userId): void { // 添加订单取消记录 OrderRecord::create([ 'order_id' => $order->id, 'object_id' => $userId, 'object_type' => MemberUser::class, 'state' => OrderRecordStatus::CANCELLED->value, 'remark' => '用户取消订单', ]); // 修改订单状态 $order->state = OrderStatus::CANCELLED->value; $order->save(); // 如果有技师,可能需要通知技师订单已取消 if ($order->coach_id) { // TODO: 发送通知给技师 // event(new OrderCancelledEvent($order)); } } /** * 处理退款 */ private function handleRefund(MemberUser $user, Order $order, float $deductAmount, bool $deductTrafficFee): void { // 关键操作:计算实际退款金额 $refundAmount = $order->payment_amount + $order->balance_amount; if ($deductTrafficFee) { $refundAmount -= $order->traffic_amount; // TODO: 记录技师路费收入 } $refundAmount -= $deductAmount; // 优先余额支付金额中扣除 $balanceRefund = min($order->balance_amount, $refundAmount); if ($balanceRefund > 0) { $this->createRefundRecords($user, $order, $balanceRefund); } // 剩余退款金额从支付金额中退还 $paymentRefund = $refundAmount - $balanceRefund; if ($paymentRefund > 0) { $this->createRefundRecords($user, $order, $paymentRefund); } // 记录平台收入 if ($deductAmount > 0) { // TODO: 添加平台收入记录 // PlatformIncome::create([...]); } } /** * 创建退款记录 */ private function createRefundRecords($user, $order, $amount, $type = 'balance'): void { $refundMethod = $type; $remark = $type == 'balance' ? '订单取消退还余额' : '订单取消退还支付金额'; // 创建退款记录 $refundRecord = $user->wallet->refundRecords()->create([ 'refund_method' => $refundMethod, 'total_refund_amount' => $order->payment_amount + $order->balance_amount, 'actual_refund_amount' => '0.00', 'wallet_balance_refund_amount' => $type == 'balance' ? $amount : '0.00', 'recharge_balance_refund_amount' => '0.00', 'remark' => $remark, 'order_id' => $order->id, ]); // 创建交易记录 $user->wallet->transRecords()->create([ 'amount' => $amount, 'owner_type' => get_class($refundRecord), 'owner_id' => $refundRecord->id, 'remark' => $remark, 'trans_type' => 'income', 'storage_type' => 'balance', 'before_balance' => $user->wallet->total_balance, 'after_balance' => $user->wallet->total_balance + $amount, 'before_recharge_balance' => '0.00', 'after_recharge_balance' => '0.00', 'trans_time' => now(), 'state' => 'success', ]); // 更新钱包余额 $user->wallet->increment('total_balance', $amount); $user->wallet->increment('available_balance', $amount); $user->wallet->save(); } /** * 记录订单取消错误日志 */ private function logCancelOrderError(Exception $e, int $userId, int $orderId): void { // 复用之前的日记录方法 Log::error('取消订单失败:', [ 'message' => $e->getMessage(), 'user_id' => $userId, 'order_id' => $orderId, 'trace' => $e->getTraceAsString(), ]); } /** * 结束订单 */ public function finishOrder(int $userId, int $orderId): array { return DB::transaction(function () use ($userId, $orderId) { try { // 1. 验证用户和订单 $order = $this->validateOrderForFinish($userId, $orderId); // 2. 验证技师状态 $coach = $this->validateCoach($order->coach_id); // 4. 完成订单 $this->completeOrder($order, $userId); // 5. 通知技师 // event(new OrderFinishedEvent($order)); return ['message' => '订单已完成']; } catch (Exception $e) { $this->logFinishOrderError($e, $userId, $orderId); throw $e; } }); } /** * 验证订单完成条件 */ private function validateOrderForFinish(int $userId, int $orderId): Order { // 验证用户状态 $user = MemberUser::where('id', $userId) ->where('state', UserStatus::OPEN->value) ->firstOrFail(); // 验证订单状态 $order = Order::where('user_id', $userId) ->where('id', $orderId) ->where('state', OrderStatus::SERVING->value) ->lockForUpdate() ->firstOrFail(); return $order; } /** * 完成订单 */ private function completeOrder(Order $order, int $userId): void { // 1. 创建订单记录 OrderRecord::create([ 'order_id' => $order->id, 'object_id' => $userId, 'object_type' => MemberUser::class, 'state' => OrderRecordStatus::COMPLETED->value, 'remark' => '服务完成', ]); // 2. 更新订单状态 $order->state = OrderStatus::FINISHED->value; $order->save(); } /** * 记录订单完成错误日志 */ private function logFinishOrderError(Exception $e, int $userId, int $orderId): void { Log::error('结束订单失败:', [ 'message' => $e->getMessage(), 'user_id' => $userId, 'order_id' => $orderId, 'trace' => $e->getTraceAsString(), ]); } /** * 确认技师离开 */ public function confirmLeave(int $userId, int $orderId): array { return DB::transaction(function () use ($userId, $orderId) { try { // 1. 参数校验 $order = Order::where('user_id', $userId) ->where('id', $orderId) ->where('state', OrderStatus::FINISHED->value) // 订单状态必须是服务结束 ->firstOrFail(); if (! $order) { throw new Exception('订单不能撤离'); } // 2. 添加订单撤离记录 OrderRecord::create([ 'order_id' => $orderId, 'object_id' => $userId, 'object_type' => MemberUser::class, 'state' => OrderRecordStatus::LEFT->value, 'remark' => '技师已离开', ]); // 3. 修改订单状态为撤离 $order->state = OrderStatus::LEFT->value; $order->save(); return ['message' => '已确技师离开']; } catch (Exception $e) { Log::error('确认技师离开失败:', [ 'message' => $e->getMessage(), 'user_id' => $userId, 'order_id' => $orderId, ]); throw $e; } }); } /** * 获取订单列表 */ public function getOrderList(int $user_id): \Illuminate\Contracts\Pagination\LengthAwarePaginator { $user = MemberUser::find($user_id); return $user->orders() ->with([ 'coach.info:id,nickname,avatar,gender', ]) ->orderBy('created_at', 'desc') ->paginate(10); } /** * 获取订单详情 */ public function getOrderDetail(int $userId, int $orderId): Order { $user = MemberUser::find($userId); return $user->orders() ->where('id', $orderId) // 需要添加订单ID条件 ->with([ 'coach.info:id,nickname,avatar,gender', 'records' => function ($query) { $query->orderBy('created_at', 'asc'); }, ]) ->firstOrFail(); } /** * 订单退款 */ public function refundOrder(int $orderId): array { // 使用 Auth::user() 获取用户对象 $user = Auth::user(); return DB::transaction(function () use ($orderId, $user) { // 查询并锁定订单 $order = Order::where('id', $orderId) ->where('user_id', $user->id) ->where('state', 'pending') ->lockForUpdate() ->firstOrFail(); // 更新订单状态 $order->state = 'refunded'; $order->save(); // 添加订单记录 OrderRecord::create([ 'order_id' => $orderId, 'object_id' => $user->id, 'object_type' => 'user', 'state' => 'refund', 'remark' => '订单退款', ]); // 创建退款记录 WalletRefundRecord::create([ 'order_id' => $orderId, 'user_id' => $user->id, 'amount' => $order->total_amount, 'state' => 'success', ]); return ['message' => '退款成功']; }); } /** * 获取代理商配置 */ public function getAgentConfig(int $agentId): array { $agent = AgentInfo::where('id', $agentId) ->where('state', 'enable') ->firstOrFail(); // $config = AgentConfig::where('agent_id', $agentId)->firstOrFail(); return [ // 'min_distance' => $config->min_distance, // 'min_fee' => $config->min_fee, // 'per_km_fee' => $config->per_km_fee ]; } /** * 获取技师配置 */ public function getCoachConfig(int $coachId): array { $coach = CoachUser::where('id', $coachId) ->where('state', 'enable') ->where('auth_state', 'passed') ->firstOrFail(); // $config = CoachConfig::where('coach_id', $coachId)->firstOrFail(); return [ // 'delivery_fee_type' => $config->delivery_fee_type, // 'charge_delivery_fee' => $config->charge_delivery_fee ]; } /** * 计算路费金额 * * @param int $coachId 技师ID * @param int $projectId 项目ID * @param int|null $agentId 代理商ID * @param float $distance 距离(公里) * @return float 路费金额 * * @throws Exception */ public function calculateDeliveryFee( int $coachId, int $projectId, ?int $agentId, float $distance ): float { try { // 1. 校验技师 $coach = CoachUser::where('state', TechnicianStatus::ACTIVE->value) ->whereHas('info', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value)) ->whereHas('real', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value)) ->whereHas('qual', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value)) ->with(['projects' => fn ($q) => $q->where('project_id', $projectId)]) ->find($coachId); abort_if(! $coach, 404, '技师不存在或状态异常'); // 2. 校验技师项目 $coachProject = $coach->projects->first(); abort_if(! $coachProject, 404, '技师项目不存在'); // 3. 判断是否免收路费 if ($coachProject->traffic_fee_type == 'free') { return 0; } // 4. 获取路费配置 $config = $this->getDeliveryFeeConfig($agentId); abort_if(! $config, 404, '路费配置不存在'); // 5. 计算路费 $fee = $this->calculateFee($distance, $config); // 6. 判断是否往返 return $coachProject->delivery_fee_type == 'round_trip' ? bcmul($fee, '2', 2) : $fee; } catch (Exception $e) { Log::error(__CLASS__.'->'.__FUNCTION__.'计算路费失败:', [ 'message' => $e->getMessage(), 'coach_id' => $coachId, 'project_id' => $projectId, 'agent_id' => $agentId, 'distance' => $distance, 'trace' => $e->getTraceAsString(), ]); throw $e; } } /** * 获取路费配置 */ private function getDeliveryFeeConfig(?int $agentId): ?object { // 优先获取代理商配置 if ($agentId) { $agent = AgentInfo::where('state', 'enable') ->with(['projectConfig']) ->find($agentId); if ($agent && $agent->projectConfig) { return $agent->projectConfig; } } // 获取系统配置 return SysConfig::where('key', 'delivery_fee')->first(); } /** * 计算路费 */ private function calculateFee(float $distance, object $config): float { // 最小距离内按起步价计算 if ($distance <= $config->min_distance) { return (float) $config->min_fee; } // 超出最小距离部分按每公里费用计算 $extraDistance = bcsub($distance, $config->min_distance, 2); $extraFee = bcmul($extraDistance, $config->per_km_fee, 2); return bcadd($config->min_fee, $extraFee, 2); } /** * 计算订单金额 * * @param int $userId 用户ID * @param int $addressId 地址ID * @param int $coachId 技师ID * @param int $projectId 项目ID * @param int $agentId 代理商ID * @param bool $useBalance 是否使用余额 * @param float $distance 距离 * @param int $lat 纬度 * @param int $lng 经度 * * @throws Exception */ public function calculateOrderAmount( int $userId, int $addressId, ?int $coachId, int $projectId, ?int $agentId = null, bool $useBalance = false, float $distance = 0, int $lat = 0, int $lng = 0 ): array { try { // 1. 参数校验 $user = MemberUser::find($userId); abort_if(! $user || $user->state != UserStatus::OPEN->value, 404, '用户不存在或状态异常'); // 2. 查询技师项目 $coach = $coachId ? $this->validateCoach($coachId) : null; $coachProject = $coach ? $coach->projects() ->where('state', ProjectStatus::OPEN->value) ->where('project_id', $projectId) ->first() : null; // 3. 查询基础项目 $project = Project::where('id', $projectId) ->where('state', ProjectStatus::OPEN->value()) ->first(); abort_if(! $project, 404, '项目不存在或状态异常'); // 4. 计算距离 if (floatval($distance) <= 0 && $coachId) { $address = $user->addresses()->find($addressId) ?? ['latitude' => $lat, 'longitude' => $lng]; $coachService = app(CoachService::class); $coachDetail = $coachService->getCoachDetail($coachId, $address['latitude'], $address['longitude']); $distance = $coachDetail['distance'] ?? 0; } // 5. 获取项目价格 $projectAmount = $this->getProjectPrice($project, $agentId, $projectId); // 6. 计算路费 $deliveryFee = $coachId ? $this->calculateDeliveryFee($coachId, $projectId, $agentId, $distance) : 0; // 7. 计算优惠券金额 $couponAmount = $this->calculateCouponAmount(); // 8. 计算总金额 $totalAmount = bcadd($projectAmount, $deliveryFee, 2); $totalAmount = bcsub($totalAmount, $couponAmount, 2); $totalAmount = max(0, $totalAmount); // 9. 计算余额支付金额 $balanceAmount = 0; $payAmount = $totalAmount; if ($useBalance && $totalAmount > 0) { $wallet = $user->wallet; abort_if(! $wallet, 404, '用户钱包不存在'); if ($wallet->available_balance >= $totalAmount) { $balanceAmount = $totalAmount; $payAmount = 0; } else { $balanceAmount = $wallet->available_balance; $payAmount = bcsub($totalAmount, $balanceAmount, 2); } } return [ 'total_amount' => $totalAmount, 'balance_amount' => $balanceAmount, 'pay_amount' => $payAmount, 'coupon_amount' => $couponAmount, 'project_amount' => $projectAmount, 'delivery_fee' => $deliveryFee, ]; } catch (Exception $e) { Log::error(__CLASS__.'->'.__FUNCTION__.'计算订单金额失败:', [ 'message' => $e->getMessage(), 'user_id' => $userId, 'project_id' => $projectId, 'trace' => $e->getTraceAsString(), ]); throw $e; } } /** * 获取项目价格 */ private function getProjectPrice($project, ?int $agentId, int $projectId): float { $price = $project->price; if ($agentId) { $agent = AgentInfo::where('state', 'enable')->find($agentId); if ($agent) { $agentProject = $agent->projects() ->where('state', 'enable') ->where('project_id', $projectId) ->first(); if ($agentProject) { $price = $agentProject->price; } } } return (float) $price; } /** * 计算优惠券金额 */ private function calculateCouponAmount(): float { $couponAmount = 0; if (request()->has('coupon_id')) { // TODO: 优惠券逻辑 } return $couponAmount; } /** * 计算支付金额分配 */ private function calculatePaymentAmounts($user, float $totalAmount, bool $useBalance): array { $balanceAmount = 0; $payAmount = $totalAmount; if ($useBalance) { $wallet = $user->wallet; if (! $wallet) { throw new Exception('用户钱包不存在'); } if ($wallet->available_balance >= $totalAmount) { $balanceAmount = $totalAmount; $payAmount = 0; } else { $balanceAmount = $wallet->available_balance; $payAmount = bcsub($totalAmount, $balanceAmount, 2); } } return [$balanceAmount, $payAmount]; } /** * 获取订单抢单池列表 * * @param int $orderId 订单ID * @return array 抢单池列表 */ public function getOrderGrabList(int $orderId): array { try { // 查询订单信息 $order = Order::where('id', $orderId) ->whereIn('state', [OrderStatus::CREATED->value]) ->firstOrFail(); // 查询抢单池列表 $grabList = $order->grabRecords()->with(['coach.info'])->get(); // 格式化返回数据 $result = []; foreach ($grabList as $grab) { $coach = $grab->coach; $result[] = [ 'id' => $grab->id, 'coach_id' => $coach->id, 'nickname' => $coach->info->nickname, 'avatar' => $coach->info->avatar, 'distance' => $grab->distance, 'created_at' => $grab->created_at->format('Y-m-d H:i:s'), ]; } return $result; } catch (\Exception $e) { Log::error('获取订单抢单池列表失败', [ 'error' => $e->getMessage(), 'order_id' => $orderId, ]); throw $e; } } /** * 指定技师 */ public function assignCoach(int $userId, int $orderId, int $coachId): bool { return DB::transaction(function () use ($userId, $orderId, $coachId) { try { // 1. 验证参数 $order = $this->validateAssignOrder($userId, $orderId); // 2. 验证技师 $coach = $this->validateCoach($coachId); // 3. 检查抢单池是否已有抢单成功记录 $existsGrabSuccess = $order->grabRecords() ->where('state', OrderGrabRecordStatus::SUCCEEDED->value) ->exists(); abort_if($existsGrabSuccess, 400, '该订单已抢单完成'); // 4. 更新订单信息 $this->updateOrderForAssign($order, $coachId); // 5. 创建订单记录(指派) $this->createAssignRecord($order, $userId); // 6. 处理支付 if ($order->payment_type === PaymentMethod::BALANCE->value) { $this->handleBalancePaymentForAssign($order, $userId, $coachId); } return true; } catch (Exception $e) { $this->logAssignCoachError($e, $userId, $orderId, $coachId); throw $e; } }); } /** * 创建指派记录 */ private function createAssignRecord(Order $order, int $userId): void { OrderRecord::create([ 'order_id' => $order->id, 'object_id' => $userId, 'object_type' => MemberUser::class, 'state' => OrderRecordStatus::ASSIGNED->value, 'remark' => '指定技师', ]); } /** * 处理指派订单的余额支付 */ private function handleBalancePaymentForAssign(Order $order, int $userId, int $coachId): void { // 验证余额 $user = MemberUser::find($userId); $wallet = $user->wallet; abort_if($wallet->available_balance < $order->balance_amount, 400, '可用余额不足'); // 扣除余额 $wallet->decrement('total_balance', $order->balance_amount); $wallet->decrement('available_balance', $order->balance_amount); // 更新订单状态 $order->update(['state' => OrderStatus::PAID->value]); // 创建钱包支付记录 WalletPaymentRecord::create([ 'order_id' => $order->id, 'wallet_id' => $wallet->id, 'payment_no' => 'balance_'.$order->id, 'payment_method' => 'balance', 'total_amount' => $order->balance_amount, 'actual_amount' => 0, 'used_wallet_balance' => $order->balance_amount, 'used_recharge_balance' => 0, 'state' => 'success', ]); // 创建接单记录 // OrderRecord::create([ // 'order_id' => $order->id, // 'object_id' => $userId, // 'object_type' => MemberUser::class, // 'state' => OrderRecordStatus::ASSIGNED->value, // 'remark' => '已分配技师', // ]); // 创建支付成功记录 OrderRecord::create([ 'order_id' => $order->id, 'object_id' => $userId, 'object_type' => MemberUser::class, 'state' => OrderRecordStatus::PAID->value, 'remark' => '余额支付成功', ]); // 更新抢单记录 $this->updateGrabRecords($order->id, $coachId); } /** * 验证指定技师的订单条件 */ private function validateAssignOrder(int $userId, int $orderId): Order { // 验证用户状态 $user = MemberUser::where('id', $userId) ->where('state', UserStatus::OPEN->value) ->firstOrFail(); // 验证订单状态 $order = $user->orders() ->where('id', $orderId) ->whereIn('state', [OrderStatus::CREATED->value, OrderStatus::PAID->value]) ->lockForUpdate() ->firstOrFail(); return $order; } /** * 更新订单信息 */ private function updateOrderForAssign(Order $order, int $coachId): void { // 修改订单技师 $order->coach_id = $coachId; // 待支付订单需要重新计算金额 if ($order->state == OrderStatus::CREATED->value) { $amounts = $this->calculateOrderAmount( $order->user_id, $order->address_id, $coachId, $order->project_id, $order->agent_id, $order->payment_type === PaymentMethod::BALANCE->value ); // 更新订单金额 $order->total_amount = $amounts['total_amount']; $order->balance_amount = $amounts['balance_amount']; $order->pay_amount = $amounts['pay_amount']; $order->discount_amount = $amounts['coupon_amount']; $order->project_amount = $amounts['project_amount']; $order->traffic_amount = $amounts['delivery_fee']; } $order->save(); } /** * 更新抢单记录 */ private function updateGrabRecords(int $orderId, int $coachId): void { OrderGrabRecord::where('order_id', $orderId) ->update([ 'state' => OrderGrabRecordStatus::SUCCEEDED->value, 'coach_id' => $coachId, ]); } /** * 记录指派技师错误日志 */ private function logAssignCoachError(Exception $e, int $userId, int $orderId, int $coachId): void { Log::error('分配技师失败:', [ 'message' => $e->getMessage(), 'user_id' => $userId, 'order_id' => $orderId, 'coach_id' => $coachId, ]); } /** * 生成订单核销码 * * @param int $userId 用户ID * @param int $orderId 订单ID * @return array 返回核销码信息 * * @throws Exception */ public function generateVerificationCode(int $userId, int $orderId): array { try { // 1. 验证用户 $user = MemberUser::where('id', $userId) ->where('state', UserStatus::OPEN->value) ->firstOrFail(); // 2. 验证订单状态和归属 $order = $user->orders() ->where('id', $orderId) ->whereIn('state', [ OrderStatus::PAID->value, OrderStatus::ACCEPTED->value, OrderStatus::DEPARTED->value, OrderStatus::ARRIVED->value, ]) ->firstOrFail(); // 3. 生成时间戳 $timestamp = time(); // 4. 生成签名 $sign = md5("order_{$order->id}_{$timestamp}_".config('app.key')); // 5. 组装二维码内容 $qrCode = "order_{$order->id}_{$timestamp}_{$sign}"; // 6. 生成二维码图片 try { // 生成JPEG格式的二维码 $qrCodeImage = QrCode::format('png') // 设置二维码大小为200px ->size(200) // 设置二维码边距为1 ->margin(1) // 设置纠错级别为最高级别H ->errorCorrection('H') // 生成二维码图片 ->generate($qrCode); // 将JPEG转为base64 $qrCodeBase64 = 'data:image/png;base64,'.base64_encode($qrCodeImage); } catch (Exception $e) { Log::error('生成二维码图片失败:', [ 'order_id' => $orderId, 'error' => $e->getMessage(), ]); // 如果二维码生成失败,仍然返回文本内容 $qrCodeBase64 = null; } // 7. 返回结果 return [ 'order_id' => $order->id, 'qr_code' => $qrCode, 'qr_image' => $qrCodeBase64, 'expired_at' => date('Y-m-d H:i:s', $timestamp + 300), 'state' => $order->state, ]; } catch (Exception $e) { Log::error('生成订单核销码失败:', [ 'user_id' => $userId, 'order_id' => $orderId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); throw $e; } } /** * 验证核销码 * * @param string $qrCode 核销码 * @param int $orderId 订单ID * * @throws Exception */ public function verifyCode(string $qrCode, int $orderId): bool { try { // 1. 验证订单 $order = Order::where('id', $orderId) ->where('state', OrderStatus::PAID->value) ->firstOrFail(); // 2. 解析二维码 // 二维码格式: order_{order_id}_{timestamp}_{sign} $parts = explode('_', $qrCode); abort_if(count($parts) !== 4, 400, '二维码格式错误'); [$prefix, $scanOrderId, $timestamp, $sign] = $parts; // 3. 验证前缀 abort_if($prefix !== 'order', 400, '无效的二维码'); // 4. 验证订单ID abort_if((int) $scanOrderId !== $order->id, 400, '二维码与订单不匹配'); // 5. 验证时间戳(二维码5分钟内有效) $qrTimestamp = (int) $timestamp; $now = time(); abort_if($now - $qrTimestamp > 300, 400, '二维码已过期'); // 6. 验证签名 $correctSign = md5("order_{$order->id}_{$timestamp}_".config('app.key')); abort_if($sign !== $correctSign, 400, '二维码签名错误'); return true; } catch (Exception $e) { Log::error('验证核销码失败:', [ 'order_id' => $orderId, 'qr_code' => $qrCode, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); throw $e; } } }