1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249 |
- <?php
- namespace App\Services\Client;
- use App\Enums\OrderGrabRecordStatus;
- use App\Enums\OrderRecordStatus;
- use App\Enums\OrderSource;
- use App\Enums\OrderStatus;
- use App\Enums\OrderType;
- use App\Enums\PaymentMethod;
- use App\Enums\ProjectStatus;
- use App\Enums\TechnicianAuthStatus;
- use App\Enums\TechnicianStatus;
- use App\Enums\UserStatus;
- use App\Models\AgentConfig;
- use App\Models\AgentInfo;
- use App\Models\CoachConfig;
- use App\Models\CoachUser;
- use App\Models\MemberUser;
- use App\Models\Order;
- use App\Models\OrderGrabRecord;
- use App\Models\OrderRecord;
- use App\Models\Project;
- use App\Models\SysConfig;
- use App\Models\User;
- use App\Models\WalletPaymentRecord;
- use App\Models\WalletRefundRecord;
- use Exception;
- use Illuminate\Support\Facades\Auth;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Redis;
- class OrderService
- {
- protected AgentService $agentService;
- protected ProjectService $projectService;
- public function __construct(
- AgentService $agentService,
- ProjectService $projectService
- ) {
- $this->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 != 'enable', 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']);
- // 获取项目详情
- $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,
- ];
- });
- } 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
- {
- 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::GRAB->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, ['service_ing', 'service_end']), 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, '可用余额不足');
- }
- // 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
- {
- $coach = CoachUser::where('id', $coachId)->first();
- $coach = 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();
- return $coach;
- }
- // 提取方法:获取原始订单
- private function getOriginalOrder($user, $orderId): Order
- {
- $originalOrder = $user->orders->where('id', $orderId)
- ->whereIn('state', ['service_ing', 'service_end'])
- ->firstOrFail();
- return $originalOrder;
- }
- // 提取方法:准备加钟订单数据
- private function prepareAddTimeData($originalOrder, $data): array
- {
- if ($originalOrder->state == 'service_ing') {
- $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
- {
- $order = new Order;
- $order->user_id = $userId;
- $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->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 == 'normal' ? 'wait_receive' : 'service_ing';
- $order->save();
- OrderRecord::create([
- 'order_id' => $order->id,
- 'object_id' => $user->id,
- 'object_type' => MemberUser::class,
- 'state' => 'pay',
- '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 == 'cancel', 400, '订单已取消');
- // 2. 处理退款
- if (in_array($order->state, ['wait_receive', 'on_the_way'])) {
- $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::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' => 'cancel',
- 'remark' => '用户取消订单',
- ]);
- // 修改订单状态
- $order->state = 'cancel';
- $order->cancel_time = now(); // 添加取消时间
- $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, 'payment');
- }
- // 记录平台收入
- 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);
- abort_if($order->state == 'service_end', 400, '订单已完成');
- // 2. 验证技师状态
- $coach = $this->validateCoach($order->coach_id);
- // 3. 验证服务时长
- $this->validateServiceDuration($order);
- // 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', 'enable')
- ->firstOrFail();
- // 验证订单状态
- $order = Order::where('user_id', $userId)
- ->where('id', $orderId)
- ->where('state', 'service_ing')
- ->lockForUpdate()
- ->firstOrFail();
- return $order;
- }
- /**
- * 验证服务时长
- */
- private function validateServiceDuration(Order $order): void
- {
- // 计算服务时长
- $serviceStartTime = $order->service_start_time ?? $order->created_at;
- $serviceDuration = now()->diffInMinutes($serviceStartTime);
- // 获取项目要求的最短服务时长
- $minDuration = $order->project->duration ?? 0;
- abort_if($serviceDuration < $minDuration, 400, "服务时长不足{$minDuration}分钟");
- }
- /**
- * 完成订单
- */
- private function completeOrder(Order $order, int $userId): void
- {
- // 1. 创建订单记录
- OrderRecord::create([
- 'order_id' => $order->id,
- 'object_id' => $userId,
- 'object_type' => MemberUser::class,
- 'state' => 'finish',
- 'remark' => '服务完成',
- ]);
- // 2. 更新订单状态
- $order->state = 'service_end';
- $order->finish_time = now();
- $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', 'service_end') // 订单状态必须是服务结束
- ->firstOrFail();
- if (! $order) {
- throw new Exception('订单不能撤离');
- }
- // 2. 添加订单撤离记录
- OrderRecord::create([
- 'order_id' => $orderId,
- 'object_id' => $userId,
- 'object_type' => MemberUser::class,
- 'state' => 'leave',
- 'remark' => '技师已离开',
- ]);
- // 3. 修改订单状态为撤离
- $order->state = 'leave';
- $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 = $this->validateCoach($coachId);
- abort_if(! $coach, 404, '技师不存在或状态异常');
- $coachProject = $coach->projects()
- ->where('state', ProjectStatus::OPEN->value)
- ->where('project_id', $projectId)
- ->first();
- abort_if(! $coachProject, 404, '技师项目不存在');
- // 3. 查询基础项目
- $project = Project::where('id', $projectId)
- ->where('state', ProjectStatus::OPEN->value())
- ->first();
- abort_if(! $project, 404, '项目不存在或状态异常');
- // 4. 计算距离
- if (floatval($distance) <= 0) {
- $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 = $this->calculateDeliveryFee($coachId, $projectId, $agentId, $distance);
- // 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 = Order::where('user_id', $userId)
- ->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,
- ]);
- }
- }
|