OrderService.php 46 KB

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