OrderService.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  1. <?php
  2. namespace App\Services\Client;
  3. use App\Enums\ProjectStatus;
  4. use App\Models\AgentConfig;
  5. use App\Models\AgentInfo;
  6. use App\Models\CoachConfig;
  7. use App\Models\CoachUser;
  8. use App\Models\MemberUser;
  9. use App\Models\Order;
  10. use App\Models\OrderGrabRecord;
  11. use App\Models\OrderRecord;
  12. use App\Models\Project;
  13. use App\Models\SysConfig;
  14. use App\Models\User;
  15. use App\Models\WalletPaymentRecord;
  16. use App\Models\WalletRefundRecord;
  17. use Exception;
  18. use Illuminate\Support\Facades\Auth;
  19. use Illuminate\Support\Facades\DB;
  20. use Illuminate\Support\Facades\Log;
  21. class OrderService
  22. {
  23. protected AgentService $agentService;
  24. protected ProjectService $projectService;
  25. public function __construct(
  26. AgentService $agentService,
  27. ProjectService $projectService
  28. ) {
  29. $this->agentService = $agentService;
  30. $this->projectService = $projectService;
  31. }
  32. /**
  33. * 订单初始化
  34. *
  35. * 初始化订单信息,包括用户钱包、技师信息、项目信息、地址信息和订单金额等
  36. *
  37. * @param int $userId 用户ID
  38. * @param array $data 订单数据
  39. * @return array 返回初始化的订单信息
  40. *
  41. * @throws \Exception 初始化失败时抛出异常
  42. */
  43. public function initialize(int $userId, array $data): array
  44. {
  45. try {
  46. // 参数验证
  47. abort_if(empty($data['project_id']), 400, '项目ID不能为空');
  48. abort_if(empty($data['coach_id']), 400, '技师ID不能为空');
  49. return DB::transaction(function () use ($userId, $data) {
  50. $user = MemberUser::find($userId);
  51. abort_if(! $user || $user->state != 'enable', 400, '用户状态异常');
  52. // 查询用户钱包
  53. $wallet = $user->wallet;
  54. // 查询默认地址
  55. $address = $user->address;
  56. $areaCode = $address ? $address->area_code : ($data['area_code'] ?? null);
  57. abort_if(empty($areaCode), 400, '区域编码不能为空');
  58. // 查询技师数据
  59. $coach = $this->validateCoach($data['coach_id']);
  60. // 获取项目详情
  61. $project = $this->projectService->getProjectDetail($data['project_id'], $areaCode);
  62. abort_if(! $project, 400, '项目不存在');
  63. // 计算订单金额
  64. $amounts = $this->calculateOrderAmount(
  65. $userId,
  66. $address?->id ?? 0,
  67. $data['coach_id'],
  68. $data['project_id'],
  69. $project->agent_id,
  70. false,
  71. $data['latitude'],
  72. $data['longitude']
  73. );
  74. return [
  75. 'wallet' => $wallet,
  76. 'coach' => $coach,
  77. 'project' => $project,
  78. 'address' => $address,
  79. 'amounts' => $amounts,
  80. ];
  81. });
  82. } catch (Exception $e) {
  83. Log::error('订单初始化失败', [
  84. 'userId' => $userId,
  85. 'data' => $data,
  86. 'error' => $e->getMessage(),
  87. 'trace' => $e->getTraceAsString(),
  88. ]);
  89. throw $e;
  90. }
  91. }
  92. /**
  93. * 创建订单
  94. */
  95. public function createOrder(int $userId, array $data): array
  96. {
  97. return DB::transaction(function () use ($userId, $data) {
  98. // 1. 参数校验
  99. $user = MemberUser::where('id', $userId)
  100. ->where('state', 'enable')
  101. ->firstOrFail();
  102. $project = Project::where('id', $data['project_id'])
  103. ->where('state', ProjectStatus::OPEN->value())
  104. ->firstOrFail();
  105. // 2. 订单类型判断
  106. $orderType = isset($data['order_id']) ? 'add_time' : 'normal';
  107. // 关键操作:验证必要参数
  108. abort_if(empty($data['project_id']), 400, '项目ID不能为空');
  109. abort_if($orderType == 'normal' && empty($data['coach_id']), 400, '技师ID不能为空');
  110. abort_if($orderType == 'normal' && empty($data['address_id']), 400, '地址ID不能为空');
  111. // 3. 验证技师
  112. // 4. 根据订单类型处理
  113. if ($orderType == 'normal') {
  114. $this->validateCoach($data['coach_id']);
  115. } else {
  116. $originalOrder = $this->getOriginalOrder($user, $data['order_id']);
  117. $data['address_id'] = $originalOrder->address_id;
  118. $this->validateCoach($originalOrder->coach_id);
  119. abort_if(! in_array($originalOrder->state, ['service_ing', 'service_end']), 400, '原订单状态不允许加钟');
  120. $data = $this->prepareAddTimeData($originalOrder, $data);
  121. }
  122. $address = $user->addresses()
  123. ->where('id', $data['address_id'])
  124. ->firstOrFail();
  125. // 5. 计算订单金额
  126. $amounts = $this->calculateOrderAmount(
  127. $userId,
  128. $address->id,
  129. $data['coach_id'],
  130. $data['project_id'],
  131. $project->agent_id,
  132. $data['use_balance'] ?? false
  133. );
  134. // 6. 验证金额和余额
  135. abort_if($amounts['total_amount'] <= 0, 400, '订单金额异常');
  136. if ($data['payment_type'] == 'balance') {
  137. $wallet = $user->wallet;
  138. abort_if($wallet->available_balance < $amounts['balance_amount'], 400, '可用余额不足');
  139. }
  140. // 7. 创建订单记录
  141. $order = $this->createOrderRecord($userId, $data, $orderType, $address, $data['payment_type'], (object) $amounts);
  142. // 8. 余额支付处理
  143. if ($order->payment_type == 'balance') {
  144. $this->handleBalancePayment($user, $order, $orderType);
  145. }
  146. return [
  147. 'order_id' => $order->id,
  148. 'payment_type' => $order->payment_type,
  149. ];
  150. });
  151. }
  152. // 提取方法:验证技师
  153. public function validateCoach(int $coachId): CoachUser
  154. {
  155. $coach = CoachUser::where('id', $coachId)
  156. ->where('state', 'enable')
  157. ->whereHas('info', fn ($q) => $q->where('state', 'approved'))
  158. ->whereHas('qual', fn ($q) => $q->where('state', 'approved'))
  159. ->whereHas('real', fn ($q) => $q->where('state', 'approved'))
  160. ->firstOrFail();
  161. return $coach;
  162. }
  163. // 提取方法:获取原始订单
  164. private function getOriginalOrder($user, $orderId): Order
  165. {
  166. $originalOrder = $user->orders->where('id', $orderId)
  167. ->whereIn('state', ['service_ing', 'service_end'])
  168. ->firstOrFail();
  169. return $originalOrder;
  170. }
  171. // 提取方法:准备加钟订单数据
  172. private function prepareAddTimeData($originalOrder, $data): array
  173. {
  174. if ($originalOrder->state == 'service_ing') {
  175. $startTime = now();
  176. } else {
  177. $startTime = now();
  178. }
  179. return [
  180. ...$data,
  181. 'order_id' => $data['order_id'],
  182. 'address_id' => $originalOrder->address_id,
  183. 'service_time' => $startTime,
  184. 'coach_id' => $originalOrder->coach_id,
  185. ];
  186. }
  187. // 提取方法:创建订单记录
  188. private function createOrderRecord($userId, $data, $orderType, $address, $payment_type, object $amounts): Order
  189. {
  190. $order = new Order;
  191. $order->user_id = $userId;
  192. $order->project_id = $data['project_id'];
  193. $order->coach_id = $data['coach_id'];
  194. $order->type = $orderType;
  195. $order->state = 'wait_pay';
  196. $order->source = 'platform';
  197. $order->total_amount = $amounts->total_amount;
  198. $order->balance_amount = $amounts->balance_amount;
  199. $order->pay_amount = $amounts->pay_amount;
  200. $order->project_amount = $amounts->project_amount;
  201. $order->traffic_amount = $orderType == 'add_time' ? 0 : $amounts->delivery_fee;
  202. $order->payment_type = ($data['use_balance'] && $amounts->pay_amount == 0) ? 'balance' : $payment_type;
  203. $order->service_time = $data['service_time'];
  204. $order->address_id = $data['address_id'];
  205. $order->longitude = $address->longitude;
  206. $order->latitude = $address->latitude;
  207. $order->location = $address->location;
  208. $order->address = $address->address;
  209. $order->area_code = $address->area_code;
  210. $order->save();
  211. OrderRecord::create([
  212. 'order_id' => $order->id,
  213. 'object_id' => $userId,
  214. 'object_type' => MemberUser::class,
  215. 'state' => $orderType == 'add_time' ? 'add_time' : 'create',
  216. 'remark' => $orderType == 'add_time' ? '加钟订单' : '创建订单',
  217. ]);
  218. return $order;
  219. }
  220. // 提取方法:处理余额支付
  221. private function handleBalancePayment($user, $order, $orderType): void
  222. {
  223. $order->state = $orderType == 'normal' ? 'wait_receive' : 'service_ing';
  224. $order->save();
  225. OrderRecord::create([
  226. 'order_id' => $order->id,
  227. 'object_id' => $user->id,
  228. 'object_type' => MemberUser::class,
  229. 'state' => 'pay',
  230. 'remark' => '余额支付',
  231. ]);
  232. $user->wallet->decrement('total_balance', $order->balance_amount);
  233. $user->wallet->decrement('available_balance', $order->balance_amount);
  234. }
  235. /**
  236. * 取消订单
  237. */
  238. public function cancelOrder(int $userId, int $orderId): array
  239. {
  240. return DB::transaction(function () use ($userId, $orderId) {
  241. try {
  242. // 1. 验证用户和订单
  243. $order = $this->validateOrderForCancel($userId, $orderId);
  244. abort_if($order->state == 'cancel', 400, '订单已取消');
  245. // 2. 处理退款
  246. if (in_array($order->state, ['wait_receive', 'on_the_way'])) {
  247. $this->handleCancelRefund($order);
  248. }
  249. // 3. 完成订单取消
  250. $this->completeCancel($order, $userId);
  251. // 4. 通知技师
  252. if ($order->coach_id) {
  253. // event(new OrderCancelledEvent($order));
  254. }
  255. return ['message' => '订单已取消'];
  256. } catch (Exception $e) {
  257. $this->logCancelOrderError($e, $userId, $orderId);
  258. throw $e;
  259. }
  260. });
  261. }
  262. /**
  263. * 验证订单取消条件
  264. */
  265. private function validateOrderForCancel(int $userId, int $orderId): Order
  266. {
  267. // 复用之前的用户验证逻辑
  268. $user = MemberUser::where('id', $userId)
  269. ->where('state', 'enable')
  270. ->firstOrFail();
  271. // 验证订单状态
  272. $order = Order::where('user_id', $userId)
  273. ->where('id', $orderId)
  274. ->whereIn('state', ['wait_pay', 'wait_receive', 'on_the_way'])
  275. ->lockForUpdate()
  276. ->firstOrFail();
  277. return $order;
  278. }
  279. /**
  280. * 处理订单取消退款
  281. */
  282. private function handleCancelRefund(Order $order): void
  283. {
  284. $user = $order->user;
  285. switch ($order->state) {
  286. case 'wait_receive': // 已接单
  287. // 扣除20%费用
  288. $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.2;
  289. $this->handleRefund($user, $order, $deductAmount, false);
  290. break;
  291. case 'on_the_way': // 已出发
  292. // 扣除50%费用并扣除路费
  293. $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.5;
  294. $this->handleRefund($user, $order, $deductAmount, true);
  295. break;
  296. case 'wait_pay':
  297. // 待支付状态直接取消,无需退款
  298. break;
  299. default:
  300. abort(400, '当前订单状态不允许取消');
  301. }
  302. }
  303. /**
  304. * 完成订单取消
  305. */
  306. private function completeCancel(Order $order, int $userId): void
  307. {
  308. // 添加订单取消记录
  309. OrderRecord::create([
  310. 'order_id' => $order->id,
  311. 'object_id' => $userId,
  312. 'object_type' => MemberUser::class,
  313. 'state' => 'cancel',
  314. 'remark' => '用户取消订单',
  315. ]);
  316. // 修改订单状态
  317. $order->state = 'cancel';
  318. $order->cancel_time = now(); // 添加取消时间
  319. $order->save();
  320. // 如果有技师,可能需要通知技师订单已取消
  321. if ($order->coach_id) {
  322. // TODO: 发送通知给技师
  323. // event(new OrderCancelledEvent($order));
  324. }
  325. }
  326. /**
  327. * 处理退款
  328. */
  329. private function handleRefund(MemberUser $user, Order $order, float $deductAmount, bool $deductTrafficFee): void
  330. {
  331. // 关键操作:计算实际退款金额
  332. $refundAmount = $order->payment_amount + $order->balance_amount;
  333. if ($deductTrafficFee) {
  334. $refundAmount -= $order->traffic_amount;
  335. // TODO: 记录技师路费收入
  336. }
  337. $refundAmount -= $deductAmount;
  338. // 优先从余额支付金额中扣除
  339. $balanceRefund = min($order->balance_amount, $refundAmount);
  340. if ($balanceRefund > 0) {
  341. $this->createRefundRecords($user, $order, $balanceRefund);
  342. }
  343. // 剩余退款金额从支付金额中退还
  344. $paymentRefund = $refundAmount - $balanceRefund;
  345. if ($paymentRefund > 0) {
  346. $this->createRefundRecords($user, $order, $paymentRefund, 'payment');
  347. }
  348. // 记录平台收入
  349. if ($deductAmount > 0) {
  350. // TODO: 添加平台收入记录
  351. // PlatformIncome::create([...]);
  352. }
  353. }
  354. /**
  355. * 创建退款记录
  356. */
  357. private function createRefundRecords($user, $order, $amount, $type = 'balance'): void
  358. {
  359. $refundMethod = $type;
  360. $remark = $type == 'balance' ? '订单取消退还余额' : '订单取消退还支付金额';
  361. // 创建退款记录
  362. $refundRecord = $user->wallet->refundRecords()->create([
  363. 'refund_method' => $refundMethod,
  364. 'total_refund_amount' => $order->payment_amount + $order->balance_amount,
  365. 'actual_refund_amount' => '0.00',
  366. 'wallet_balance_refund_amount' => $type == 'balance' ? $amount : '0.00',
  367. 'recharge_balance_refund_amount' => '0.00',
  368. 'remark' => $remark,
  369. 'order_id' => $order->id,
  370. ]);
  371. // 创建交易记录
  372. $user->wallet->transRecords()->create([
  373. 'amount' => $amount,
  374. 'owner_type' => get_class($refundRecord),
  375. 'owner_id' => $refundRecord->id,
  376. 'remark' => $remark,
  377. 'trans_type' => 'income',
  378. 'storage_type' => 'balance',
  379. 'before_balance' => $user->wallet->total_balance,
  380. 'after_balance' => $user->wallet->total_balance + $amount,
  381. 'before_recharge_balance' => '0.00',
  382. 'after_recharge_balance' => '0.00',
  383. 'trans_time' => now(),
  384. 'state' => 'success',
  385. ]);
  386. // 更新钱包余额
  387. $user->wallet->increment('total_balance', $amount);
  388. $user->wallet->increment('available_balance', $amount);
  389. $user->wallet->save();
  390. }
  391. /**
  392. * 记录订单取消错误日志
  393. */
  394. private function logCancelOrderError(Exception $e, int $userId, int $orderId): void
  395. {
  396. // 复用之前的日志记录方法
  397. Log::error('取消订单失败:', [
  398. 'message' => $e->getMessage(),
  399. 'user_id' => $userId,
  400. 'order_id' => $orderId,
  401. 'trace' => $e->getTraceAsString(),
  402. ]);
  403. }
  404. /**
  405. * 结束订单
  406. */
  407. public function finishOrder(int $userId, int $orderId): array
  408. {
  409. return DB::transaction(function () use ($userId, $orderId) {
  410. try {
  411. // 1. 验证用户和订单
  412. $order = $this->validateOrderForFinish($userId, $orderId);
  413. abort_if($order->state == 'service_end', 400, '订单已完成');
  414. // 2. 验证技师状态
  415. $coach = $this->validateCoach($order->coach_id);
  416. // 3. 验证服务时长
  417. $this->validateServiceDuration($order);
  418. // 4. 完成订单
  419. $this->completeOrder($order, $userId);
  420. // 5. 通知技师
  421. // event(new OrderFinishedEvent($order));
  422. return ['message' => '订单已完成'];
  423. } catch (Exception $e) {
  424. $this->logFinishOrderError($e, $userId, $orderId);
  425. throw $e;
  426. }
  427. });
  428. }
  429. /**
  430. * 验证订单完成条件
  431. */
  432. private function validateOrderForFinish(int $userId, int $orderId): Order
  433. {
  434. // 验证用户状态
  435. $user = MemberUser::where('id', $userId)
  436. ->where('state', 'enable')
  437. ->firstOrFail();
  438. // 验证订单状态
  439. $order = Order::where('user_id', $userId)
  440. ->where('id', $orderId)
  441. ->where('state', 'service_ing')
  442. ->lockForUpdate()
  443. ->firstOrFail();
  444. return $order;
  445. }
  446. /**
  447. * 验证服务时长
  448. */
  449. private function validateServiceDuration(Order $order): void
  450. {
  451. // 计算服务时长
  452. $serviceStartTime = $order->service_start_time ?? $order->created_at;
  453. $serviceDuration = now()->diffInMinutes($serviceStartTime);
  454. // 获取项目要求的最短服务时长
  455. $minDuration = $order->project->duration ?? 0;
  456. abort_if($serviceDuration < $minDuration, 400, "服务时长不足{$minDuration}分钟");
  457. }
  458. /**
  459. * 完成订单
  460. */
  461. private function completeOrder(Order $order, int $userId): void
  462. {
  463. // 1. 创建订单记录
  464. OrderRecord::create([
  465. 'order_id' => $order->id,
  466. 'object_id' => $userId,
  467. 'object_type' => MemberUser::class,
  468. 'state' => 'finish',
  469. 'remark' => '服务完成',
  470. ]);
  471. // 2. 更新订单状态
  472. $order->state = 'service_end';
  473. $order->finish_time = now();
  474. $order->save();
  475. }
  476. /**
  477. * 记录订单完成错误日志
  478. */
  479. private function logFinishOrderError(Exception $e, int $userId, int $orderId): void
  480. {
  481. Log::error('结束订单失败:', [
  482. 'message' => $e->getMessage(),
  483. 'user_id' => $userId,
  484. 'order_id' => $orderId,
  485. 'trace' => $e->getTraceAsString(),
  486. ]);
  487. }
  488. /**
  489. * 确认技师离开
  490. */
  491. public function confirmLeave(int $userId, int $orderId): array
  492. {
  493. return DB::transaction(function () use ($userId, $orderId) {
  494. try {
  495. // 1. 参数校验
  496. $order = Order::where('user_id', $userId)
  497. ->where('id', $orderId)
  498. ->where('state', 'service_end') // 订单状态必须是服务结束
  499. ->firstOrFail();
  500. if (! $order) {
  501. throw new Exception('订单不能撤离');
  502. }
  503. // 2. 添加订单撤离记录
  504. OrderRecord::create([
  505. 'order_id' => $orderId,
  506. 'object_id' => $userId,
  507. 'object_type' => MemberUser::class,
  508. 'state' => 'leave',
  509. 'remark' => '技师已离开',
  510. ]);
  511. // 3. 修改订单状态为撤离
  512. $order->state = 'leave';
  513. $order->save();
  514. return ['message' => '已确技师离开'];
  515. } catch (Exception $e) {
  516. Log::error('确认技师离开失败:', [
  517. 'message' => $e->getMessage(),
  518. 'user_id' => $userId,
  519. 'order_id' => $orderId,
  520. ]);
  521. throw $e;
  522. }
  523. });
  524. }
  525. /**
  526. * 获取订单列表
  527. */
  528. public function getOrderList(int $user_id): \Illuminate\Contracts\Pagination\LengthAwarePaginator
  529. {
  530. $user = MemberUser::find($user_id);
  531. return $user->orders()
  532. ->with([
  533. 'coach.info:id,nickname,avatar,gender',
  534. ])
  535. ->orderBy('created_at', 'desc')
  536. ->paginate(10);
  537. }
  538. /**
  539. * 获取订单详情
  540. */
  541. public function getOrderDetail(int $userId, int $orderId): Order
  542. {
  543. $user = MemberUser::find($userId);
  544. return $user->orders()
  545. ->where('id', $orderId) // 需要添加订单ID条件
  546. ->with([
  547. 'coach.info:id,nickname,avatar,gender',
  548. 'records' => function ($query) {
  549. $query->orderBy('created_at', 'asc');
  550. },
  551. ])
  552. ->firstOrFail();
  553. }
  554. /**
  555. * 订单退款
  556. */
  557. public function refundOrder(int $orderId): array
  558. {
  559. // 使用 Auth::user() 获取用户对象
  560. $user = Auth::user();
  561. return DB::transaction(function () use ($orderId, $user) {
  562. // 查询并锁定订单
  563. $order = Order::where('id', $orderId)
  564. ->where('user_id', $user->id)
  565. ->where('state', 'pending')
  566. ->lockForUpdate()
  567. ->firstOrFail();
  568. // 更新订单状态
  569. $order->state = 'refunded';
  570. $order->save();
  571. // 添加订单记录
  572. OrderRecord::create([
  573. 'order_id' => $orderId,
  574. 'object_id' => $user->id,
  575. 'object_type' => 'user',
  576. 'state' => 'refund',
  577. 'remark' => '订单退款',
  578. ]);
  579. // 创建退款记录
  580. WalletRefundRecord::create([
  581. 'order_id' => $orderId,
  582. 'user_id' => $user->id,
  583. 'amount' => $order->total_amount,
  584. 'state' => 'success',
  585. ]);
  586. return ['message' => '退款成功'];
  587. });
  588. }
  589. /**
  590. * 获取代理商配置
  591. */
  592. public function getAgentConfig(int $agentId): array
  593. {
  594. $agent = AgentInfo::where('id', $agentId)
  595. ->where('state', 'enable')
  596. ->firstOrFail();
  597. // $config = AgentConfig::where('agent_id', $agentId)->firstOrFail();
  598. return [
  599. // 'min_distance' => $config->min_distance,
  600. // 'min_fee' => $config->min_fee,
  601. // 'per_km_fee' => $config->per_km_fee
  602. ];
  603. }
  604. /**
  605. * 获取技师配置
  606. */
  607. public function getCoachConfig(int $coachId): array
  608. {
  609. $coach = CoachUser::where('id', $coachId)
  610. ->where('state', 'enable')
  611. ->where('auth_state', 'passed')
  612. ->firstOrFail();
  613. // $config = CoachConfig::where('coach_id', $coachId)->firstOrFail();
  614. return [
  615. // 'delivery_fee_type' => $config->delivery_fee_type,
  616. // 'charge_delivery_fee' => $config->charge_delivery_fee
  617. ];
  618. }
  619. /**
  620. * 计算路费金额
  621. *
  622. * @param int $coachId 技师ID
  623. * @param int $projectId 项目ID
  624. * @param int|null $agentId 代理商ID
  625. * @param float $distance 距离(公里)
  626. * @return float 路费金额
  627. *
  628. * @throws Exception
  629. */
  630. public function calculateDeliveryFee(
  631. int $coachId,
  632. int $projectId,
  633. ?int $agentId,
  634. float $distance
  635. ): float {
  636. try {
  637. // 1. 校验技师
  638. $coach = CoachUser::where('state', 'enable')
  639. ->whereHas('info', fn ($q) => $q->where('state', 'approved'))
  640. ->whereHas('real', fn ($q) => $q->where('state', 'approved'))
  641. ->whereHas('qual', fn ($q) => $q->where('state', 'approved'))
  642. ->with(['projects' => fn ($q) => $q->where('project_id', $projectId)])
  643. ->find($coachId);
  644. abort_if(! $coach, 404, '技师不存在或状态异常');
  645. // 2. 校验技师项目
  646. $coachProject = $coach->projects->first();
  647. abort_if(! $coachProject, 404, '技师项目不存在');
  648. // 3. 判断是否免收路费
  649. if ($coachProject->traffic_fee_type == 'free') {
  650. return 0;
  651. }
  652. // 4. 获取路费配置
  653. $config = $this->getDeliveryFeeConfig($agentId);
  654. abort_if(! $config, 404, '路费配置不存在');
  655. // 5. 计算路费
  656. $fee = $this->calculateFee($distance, $config);
  657. // 6. 判断是否往返
  658. return $coachProject->delivery_fee_type == 'round_trip'
  659. ? bcmul($fee, '2', 2)
  660. : $fee;
  661. } catch (Exception $e) {
  662. Log::error(__CLASS__.'->'.__FUNCTION__.'计算路费失败:', [
  663. 'message' => $e->getMessage(),
  664. 'coach_id' => $coachId,
  665. 'project_id' => $projectId,
  666. 'agent_id' => $agentId,
  667. 'distance' => $distance,
  668. 'trace' => $e->getTraceAsString(),
  669. ]);
  670. throw $e;
  671. }
  672. }
  673. /**
  674. * 获取路费配置
  675. */
  676. private function getDeliveryFeeConfig(?int $agentId): ?object
  677. {
  678. // 优先获取代理商配置
  679. if ($agentId) {
  680. $agent = AgentInfo::where('state', 'enable')
  681. ->with(['projectConfig'])
  682. ->find($agentId);
  683. if ($agent && $agent->projectConfig) {
  684. return $agent->projectConfig;
  685. }
  686. }
  687. // 获取系统配置
  688. return SysConfig::where('key', 'delivery_fee')->first();
  689. }
  690. /**
  691. * 计算路费
  692. */
  693. private function calculateFee(float $distance, object $config): float
  694. {
  695. // 最小距离内按起步价计算
  696. if ($distance <= $config->min_distance) {
  697. return (float) $config->min_fee;
  698. }
  699. // 超出最小距离部分按每公里费用计算
  700. $extraDistance = bcsub($distance, $config->min_distance, 2);
  701. $extraFee = bcmul($extraDistance, $config->per_km_fee, 2);
  702. return bcadd($config->min_fee, $extraFee, 2);
  703. }
  704. /**
  705. * 计算订单金额
  706. *
  707. * @param int $userId 用户ID
  708. * @param int $addressId 地址ID
  709. * @param int $coachId 技师ID
  710. * @param int $projectId 项目ID
  711. * @param int $agentId 代理商ID
  712. * @param bool $useBalance 是否使用余额
  713. * @param float $distance 距离
  714. * @param int $lat 纬度
  715. * @param int $lng 经度
  716. *
  717. * @throws Exception
  718. */
  719. public function calculateOrderAmount(
  720. int $userId,
  721. int $addressId,
  722. int $coachId,
  723. int $projectId,
  724. ?int $agentId = null,
  725. bool $useBalance = false,
  726. float $distance = 0,
  727. int $lat = 0,
  728. int $lng = 0
  729. ): array {
  730. try {
  731. // 1. 参数校验
  732. $user = MemberUser::find($userId);
  733. abort_if(! $user || $user->state != 'enable', 404, '用户不存在或状态异常');
  734. // 2. 查询技师项目
  735. $coach = $this->validateCoach($coachId);
  736. abort_if(! $coach, 404, '技师不存在或状态异常');
  737. $coachProject = $coach->projects()
  738. ->where('state', 'enable')
  739. ->where('project_id', $projectId)
  740. ->first();
  741. abort_if(! $coachProject, 404, '技师项目不存在');
  742. // 3. 查询基础项目
  743. $project = Project::where('id', $projectId)
  744. ->where('state', ProjectStatus::OPEN->value())
  745. ->first();
  746. abort_if(! $project, 404, '项目不存在或状态异常');
  747. // 4. 计算距离
  748. if (floatval($distance) <= 0) {
  749. $address = $user->addresses()->find($addressId) ?? ['latitude' => $lat, 'longitude' => $lng];
  750. $coachService = app(CoachService::class);
  751. $coachDetail = $coachService->getCoachDetail($coachId, $address['latitude'], $address['longitude']);
  752. $distance = $coachDetail['distance'] ?? 0;
  753. }
  754. // 5. 获取项目价格
  755. $projectAmount = $this->getProjectPrice($project, $agentId, $projectId);
  756. // 6. 计算路费
  757. $deliveryFee = $this->calculateDeliveryFee($coachId, $projectId, $agentId, $distance);
  758. // 7. 计算优惠券金额
  759. $couponAmount = $this->calculateCouponAmount();
  760. // 8. 计算总金额
  761. $totalAmount = bcadd($projectAmount, $deliveryFee, 2);
  762. $totalAmount = bcsub($totalAmount, $couponAmount, 2);
  763. $totalAmount = max(0, $totalAmount);
  764. // 9. 计算余额支付金额
  765. $balanceAmount = 0;
  766. $payAmount = $totalAmount;
  767. if ($useBalance && $totalAmount > 0) {
  768. $wallet = $user->wallet;
  769. abort_if(! $wallet, 404, '用户钱包不存在');
  770. if ($wallet->available_balance >= $totalAmount) {
  771. $balanceAmount = $totalAmount;
  772. $payAmount = 0;
  773. } else {
  774. $balanceAmount = $wallet->available_balance;
  775. $payAmount = bcsub($totalAmount, $balanceAmount, 2);
  776. }
  777. }
  778. return [
  779. 'total_amount' => $totalAmount,
  780. 'balance_amount' => $balanceAmount,
  781. 'pay_amount' => $payAmount,
  782. 'coupon_amount' => $couponAmount,
  783. 'project_amount' => $projectAmount,
  784. 'delivery_fee' => $deliveryFee,
  785. ];
  786. } catch (Exception $e) {
  787. Log::error(__CLASS__.'->'.__FUNCTION__.'计算订单金额失败:', [
  788. 'message' => $e->getMessage(),
  789. 'user_id' => $userId,
  790. 'project_id' => $projectId,
  791. 'trace' => $e->getTraceAsString(),
  792. ]);
  793. throw $e;
  794. }
  795. }
  796. /**
  797. * 获取项目价格
  798. */
  799. private function getProjectPrice($project, ?int $agentId, int $projectId): float
  800. {
  801. $price = $project->price;
  802. if ($agentId) {
  803. $agent = AgentInfo::where('state', 'enable')->find($agentId);
  804. if ($agent) {
  805. $agentProject = $agent->projects()
  806. ->where('state', 'enable')
  807. ->where('project_id', $projectId)
  808. ->first();
  809. if ($agentProject) {
  810. $price = $agentProject->price;
  811. }
  812. }
  813. }
  814. return (float) $price;
  815. }
  816. /**
  817. * 计算优惠券金额
  818. */
  819. private function calculateCouponAmount(): float
  820. {
  821. $couponAmount = 0;
  822. if (request()->has('coupon_id')) {
  823. // TODO: 优惠券逻辑
  824. }
  825. return $couponAmount;
  826. }
  827. /**
  828. * 计算支付金额分配
  829. */
  830. private function calculatePaymentAmounts($user, float $totalAmount, bool $useBalance): array
  831. {
  832. $balanceAmount = 0;
  833. $payAmount = $totalAmount;
  834. if ($useBalance) {
  835. $wallet = $user->wallet;
  836. if (! $wallet) {
  837. throw new Exception('用户钱包不存在');
  838. }
  839. if ($wallet->available_balance >= $totalAmount) {
  840. $balanceAmount = $totalAmount;
  841. $payAmount = 0;
  842. } else {
  843. $balanceAmount = $wallet->available_balance;
  844. $payAmount = bcsub($totalAmount, $balanceAmount, 2);
  845. }
  846. }
  847. return [$balanceAmount, $payAmount];
  848. }
  849. /**
  850. * 获取订单抢单池列表
  851. *
  852. * @param int $orderId 订单ID
  853. * @return array 抢单池列表
  854. */
  855. public function getOrderGrabList(int $orderId): array
  856. {
  857. try {
  858. // 查询订单信息
  859. $order = Order::where('id', $orderId)
  860. ->whereIn('state', ['wait_pay', 'wait_service'])
  861. ->firstOrFail();
  862. // 查询抢单池列表
  863. $grabList = $order->grabRecords()->with(['coach.info'])->get();
  864. // 格式化返回数据
  865. $result = [];
  866. foreach ($grabList as $grab) {
  867. $coach = $grab->coach;
  868. $result[] = [
  869. 'id' => $grab->id,
  870. 'coach_id' => $coach->id,
  871. 'nickname' => $coach->info->nickname,
  872. 'avatar' => $coach->info->avatar,
  873. 'created_at' => $grab->created_at->format('Y-m-d H:i:s'),
  874. ];
  875. }
  876. return $result;
  877. } catch (\Exception $e) {
  878. Log::error('获取订单抢单池列表失败', [
  879. 'error' => $e->getMessage(),
  880. 'order_id' => $orderId,
  881. ]);
  882. throw $e;
  883. }
  884. }
  885. /**
  886. * 指定技师
  887. */
  888. public function assignCoach(int $userId, int $orderId, int $coachId): bool
  889. {
  890. return DB::transaction(function () use ($userId, $orderId, $coachId) {
  891. try {
  892. // 1. 验证参数
  893. $order = $this->validateAssignOrder($userId, $orderId);
  894. // 2. 验证技师
  895. $coach = $this->validateCoach($coachId);
  896. // 3. 更新订单信息
  897. $this->updateOrderForAssign($order, $coachId);
  898. // 4. 创建订单记录(指派)
  899. $this->createAssignRecord($order, $userId);
  900. // 5. 处理支付
  901. if ($order->payment_type == 'balance') {
  902. $this->handleBalancePaymentForAssign($order, $userId, $coachId);
  903. }
  904. return true;
  905. } catch (Exception $e) {
  906. $this->logAssignCoachError($e, $userId, $orderId, $coachId);
  907. throw $e;
  908. }
  909. });
  910. }
  911. /**
  912. * 创建指派记录
  913. */
  914. private function createAssignRecord(Order $order, int $userId): void
  915. {
  916. OrderRecord::create([
  917. 'order_id' => $order->id,
  918. 'object_id' => $userId,
  919. 'object_type' => MemberUser::class,
  920. 'state' => 'assigned',
  921. 'remark' => '指定技师',
  922. ]);
  923. }
  924. /**
  925. * 处理指派订单的余额支付
  926. */
  927. private function handleBalancePaymentForAssign(Order $order, int $userId, int $coachId): void
  928. {
  929. // 验证余额
  930. $user = MemberUser::find($userId);
  931. $wallet = $user->wallet;
  932. abort_if($wallet->available_balance < $order->balance_amount, 400, '可用余额不足');
  933. // 扣除余额
  934. $wallet->decrement('total_balance', $order->balance_amount);
  935. $wallet->decrement('available_balance', $order->balance_amount);
  936. // 更新订单状态
  937. $order->update(['state' => 'wait_service']);
  938. // 创建钱包支付记录
  939. WalletPaymentRecord::create([
  940. 'order_id' => $order->id,
  941. 'wallet_id' => $wallet->id,
  942. 'payment_no' => 'balance_'.$order->id,
  943. 'payment_method' => 'balance',
  944. 'total_amount' => $order->balance_amount,
  945. 'actual_amount' => 0,
  946. 'used_wallet_balance' => $order->balance_amount,
  947. 'used_recharge_balance' => 0,
  948. 'state' => 'success',
  949. ]);
  950. // 创建支付成功记录
  951. OrderRecord::create([
  952. 'order_id' => $order->id,
  953. 'object_id' => $userId,
  954. 'object_type' => MemberUser::class,
  955. 'state' => 'pay',
  956. 'remark' => '余额支付成功',
  957. ]);
  958. // 创建接单记录
  959. OrderRecord::create([
  960. 'order_id' => $order->id,
  961. 'object_type' => CoachUser::class,
  962. 'object_id' => $coachId,
  963. 'state' => 'accepted',
  964. 'remark' => '抢单成功',
  965. ]);
  966. // 更新抢单记录
  967. $this->updateGrabRecords($order->id, $coachId);
  968. }
  969. /**
  970. * 验证指定技师的订单条件
  971. */
  972. private function validateAssignOrder(int $userId, int $orderId): Order
  973. {
  974. // 验证用户状态
  975. $user = MemberUser::where('id', $userId)
  976. ->where('state', 'enable')
  977. ->firstOrFail();
  978. // 验证订单状态
  979. $order = Order::where('user_id', $userId)
  980. ->where('id', $orderId)
  981. ->whereIn('state', ['wait_pay', 'wait_receive'])
  982. ->lockForUpdate()
  983. ->firstOrFail();
  984. return $order;
  985. }
  986. /**
  987. * 更新订单信息
  988. */
  989. private function updateOrderForAssign(Order $order, int $coachId): void
  990. {
  991. // 修改订单技师
  992. $order->coach_id = $coachId;
  993. // 待支付订单需要重新计算金额
  994. if ($order->state == 'wait_pay') {
  995. $amounts = $this->calculateOrderAmount(
  996. $order->user_id,
  997. $order->address_id,
  998. $coachId,
  999. $order->project_id,
  1000. $order->agent_id,
  1001. $order->payment_type === 'balance'
  1002. );
  1003. // 更新订单金额
  1004. $order->total_amount = $amounts['total_amount'];
  1005. $order->balance_amount = $amounts['balance_amount'];
  1006. $order->pay_amount = $amounts['pay_amount'];
  1007. $order->discount_amount = $amounts['coupon_amount'];
  1008. $order->project_amount = $amounts['project_amount'];
  1009. $order->traffic_amount = $amounts['delivery_fee'];
  1010. }
  1011. $order->save();
  1012. }
  1013. /**
  1014. * 更新抢单记录
  1015. */
  1016. private function updateGrabRecords(int $orderId, int $coachId): void
  1017. {
  1018. OrderGrabRecord::where('order_id', $orderId)
  1019. ->update([
  1020. 'state' => 'success',
  1021. 'coach_id' => $coachId,
  1022. ]);
  1023. }
  1024. /**
  1025. * 记录指派技师错误日志
  1026. */
  1027. private function logAssignCoachError(Exception $e, int $userId, int $orderId, int $coachId): void
  1028. {
  1029. Log::error('分配技师失败:', [
  1030. 'message' => $e->getMessage(),
  1031. 'user_id' => $userId,
  1032. 'order_id' => $orderId,
  1033. 'coach_id' => $coachId,
  1034. ]);
  1035. }
  1036. }