OrderService.php 37 KB

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