OrderService.php 45 KB

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