where('key', 'order.timeout_minutes')->value('value'); if ($minutes === null || $minutes === '') { $minutes = Db::name('fc_config')->where('key', 'trade.order_timeout')->value('value'); } $val = (int)$minutes; if ($val > 0) { return $val * 60; } return self::DEFAULT_TIMEOUT_SECONDS; } /** * 判断订单是否超时 * * @param array $order fc_order 行 * @param int|null $now 当前时间戳 * @return bool */ public static function isExpired(array $order, $now = null) { if (empty($order) || (string)$order['status'] !== 'pending') { return false; } $now = $now ?: time(); $createtime = isset($order['createtime']) ? (int)$order['createtime'] : 0; if ($createtime <= 0) { return false; } $timeout = self::getTimeoutSeconds(); return ($now - $createtime) >= $timeout; } /** * 关闭订单(原子操作,支持主动取消或超时自动关闭) * * @param array $order fc_order 行(至少含 id、order_no、user_id、type、status、related_id) * @param string $reason 关闭原因:user_cancel(用户主动取消)、timeout_auto(超时自动关闭)、admin_cancel(后台取消) * @return bool 是否成功关闭 */ public static function close(array $order, $reason = 'user_cancel') { $orderId = isset($order['id']) ? (int)$order['id'] : 0; $orderNo = isset($order['order_no']) ? (string)$order['order_no'] : ''; if ($orderId <= 0 || $orderNo === '') { return false; } $now = time(); $closed = false; try { Db::transaction(function () use ($orderId, $orderNo, $order, $reason, $now, &$closed) { // 条件更新,保证只有 pending 状态的订单才能转为 closed,天然防并发与重复关闭 $affected = Db::name('fc_order') ->where('id', $orderId) ->where('status', 'pending') ->update([ 'status' => 'closed', 'refund_reason' => ($reason === 'user_cancel' ? '用户主动取消' : ($reason === 'timeout_auto' ? '超时未支付自动关闭' : '管理员关闭')), 'updatetime' => $now, ]); if (!$affected) { $closed = false; return; } $closed = true; // 1. 释放优惠券(若有) CouponService::release($orderNo); // 2. 关联业务的回收处理 $type = isset($order['type']) ? (string)$order['type'] : ''; $relatedId = isset($order['related_id']) ? (int)$order['related_id'] : 0; // 若是未付款的发布信息,将关联的草稿或帖子置为下架/关闭,避免未付款直接审核显示 if ($type === 'post' && $relatedId > 0) { Db::name('fc_post') ->where('id', $relatedId) ->where('user_id', (int)$order['user_id']) ->where('audit_status', 0) // 仅当尚未审核通过时 ->update([ 'status' => 0, // 设为下架/关闭 'updatetime' => $now, ]); } // 3. 发送站内消息通知 $userId = isset($order['user_id']) ? (int)$order['user_id'] : 0; if ($userId > 0) { $title = $reason === 'user_cancel' ? '订单已取消' : '订单已超时关闭'; $orderTitle = isset($order['title']) ? (string)$order['title'] : '同城订单'; $content = $reason === 'user_cancel' ? "您的订单【{$orderTitle}】(订单号:{$orderNo})已主动取消。" : "您的订单【{$orderTitle}】(订单号:{$orderNo})因超过支付时效已自动关闭,锁定的优惠券已退回。"; Db::name('fc_message')->insert([ 'user_id' => $userId, 'type' => 'system', 'title' => $title, 'content' => $content, 'isread' => 0, 'readtime' => null, 'createtime' => $now, ]); } }); } catch (\Throwable $e) { Log::record("订单关闭失败:订单 {$orderNo},原因:{$e->getMessage()}", 'error'); return false; } return $closed; } /** * 惰性检查并在超时时代为关闭 * * @param array $order fc_order 行 * @return array 最新订单数据 */ public static function checkAndCloseIfExpired(array $order) { if (empty($order) || (string)$order['status'] !== 'pending') { return $order; } if (self::isExpired($order)) { if (self::close($order, 'timeout_auto')) { $order['status'] = 'closed'; $order['updatetime'] = time(); } } return $order; } }