input('keyword', '')); $status = trim((string)$request->input('status', '')); $query = Db::name('enterprise_customers')->order('id', 'desc'); if ($keyword !== '') { $query->where(function ($builder) use ($keyword) { $builder->whereRaw( '(customer_code LIKE :keyword_code OR customer_name LIKE :keyword_name OR contact_name LIKE :keyword_contact OR contact_mobile LIKE :keyword_mobile)', [ 'keyword_code' => "%{$keyword}%", 'keyword_name' => "%{$keyword}%", 'keyword_contact' => "%{$keyword}%", 'keyword_mobile' => "%{$keyword}%", ] ); }); } if (in_array($status, ['enabled', 'disabled'], true)) { $query->where('status', $status); } $rows = $query->select()->toArray(); $customerIds = array_map(static fn(array $item) => (int)$item['id'], $rows); $appCountMap = $this->countMap('enterprise_customer_apps', $customerIds); $orderCountMap = $this->countMap('enterprise_customer_order_refs', $customerIds); $eventCountMap = $this->countMap('enterprise_order_events', $customerIds); return api_success([ 'list' => array_map(function (array $item) use ($appCountMap, $orderCountMap, $eventCountMap) { $customer = $this->customerService()->formatCustomer($item); $id = (int)$customer['id']; $customer['app_count'] = (int)($appCountMap[$id] ?? 0); $customer['order_count'] = (int)($orderCountMap[$id] ?? 0); $customer['event_count'] = (int)($eventCountMap[$id] ?? 0); return $customer; }, $rows), ]); } public function detail(Request $request) { $id = (int)$request->input('id', 0); if ($id <= 0) { return api_error('客户 ID 不能为空', 422); } $customer = Db::name('enterprise_customers')->where('id', $id)->find(); if (!$customer) { return api_error('客户不存在', 404); } $apps = Db::name('enterprise_customer_apps') ->where('customer_id', $id) ->order('id', 'desc') ->select() ->toArray(); return api_success([ 'customer' => $this->customerService()->formatCustomer($customer), 'apps' => array_map(fn(array $item) => $this->customerService()->formatApp($item), $apps), ]); } public function save(Request $request) { $id = (int)$request->input('id', 0); $customerName = trim((string)$request->input('customer_name', '')); if ($customerName === '') { return api_error('客户名称不能为空', 422); } $status = trim((string)$request->input('status', 'enabled')); if (!in_array($status, ['enabled', 'disabled'], true)) { return api_error('客户状态不正确', 422); } $webhookUrl = trim((string)$request->input('webhook_url', '')); if ($webhookUrl !== '' && !preg_match('/^https?:\/\//i', $webhookUrl)) { return api_error('Webhook URL 必须以 http 或 https 开头', 422); } $now = date('Y-m-d H:i:s'); $payload = [ 'customer_name' => $customerName, 'contact_name' => trim((string)$request->input('contact_name', '')), 'contact_mobile' => trim((string)$request->input('contact_mobile', '')), 'contact_email' => trim((string)$request->input('contact_email', '')), 'settlement_type' => 'monthly', 'webhook_url' => $webhookUrl, 'webhook_enabled' => $request->input('webhook_enabled', false) ? 1 : 0, 'status' => $status, 'remark' => trim((string)$request->input('remark', '')), 'updated_at' => $now, ]; Db::startTrans(); try { if ($id > 0) { $customer = Db::name('enterprise_customers')->where('id', $id)->find(); if (!$customer) { Db::rollback(); return api_error('客户不存在', 404); } Db::name('enterprise_customers')->where('id', $id)->update($payload); } else { $payload['customer_code'] = $this->customerService()->generateCustomerCode(); $payload['created_at'] = $now; $id = (int)Db::name('enterprise_customers')->insertGetId($payload); } $customer = Db::name('enterprise_customers')->where('id', $id)->find(); if ($customer) { $this->customerService()->ensureVirtualUser($customer); } Db::commit(); } catch (\Throwable $e) { Db::rollback(); return api_error('客户保存失败', 500, [ 'detail' => $e->getMessage(), ]); } return api_success([ 'id' => $id, ], $request->input('id', 0) ? '客户已更新' : '客户已创建'); } public function createApp(Request $request) { $customerId = (int)$request->input('customer_id', 0); $appName = trim((string)$request->input('app_name', '默认应用')); if ($customerId <= 0) { return api_error('客户 ID 不能为空', 422); } if ($appName === '') { $appName = '默认应用'; } $customer = Db::name('enterprise_customers')->where('id', $customerId)->find(); if (!$customer) { return api_error('客户不存在', 404); } $secret = $this->customerService()->generateAppSecret(); $now = date('Y-m-d H:i:s'); $appId = (int)Db::name('enterprise_customer_apps')->insertGetId([ 'customer_id' => $customerId, 'app_name' => $appName, 'app_key' => $this->customerService()->generateAppKey(), 'app_secret_cipher' => $this->customerService()->encryptSecret($secret), 'secret_last4' => substr($secret, -4), 'status' => 'enabled', 'last_used_at' => null, 'created_at' => $now, 'updated_at' => $now, ]); $app = Db::name('enterprise_customer_apps')->where('id', $appId)->find(); return api_success([ 'app' => $this->customerService()->formatApp($app), 'app_secret' => $secret, ], '应用 Key 已创建,请立即复制保存 Secret'); } public function updateAppStatus(Request $request) { $id = (int)$request->input('id', 0); $status = trim((string)$request->input('status', '')); if ($id <= 0 || !in_array($status, ['enabled', 'disabled'], true)) { return api_error('应用 ID 或状态不正确', 422); } $app = Db::name('enterprise_customer_apps')->where('id', $id)->find(); if (!$app) { return api_error('应用不存在', 404); } Db::name('enterprise_customer_apps')->where('id', $id)->update([ 'status' => $status, 'updated_at' => date('Y-m-d H:i:s'), ]); return api_success([ 'id' => $id, 'status' => $status, ], $status === 'enabled' ? '应用已启用' : '应用已停用'); } public function resetAppSecret(Request $request) { $id = (int)$request->input('id', 0); if ($id <= 0) { return api_error('应用 ID 不能为空', 422); } $app = Db::name('enterprise_customer_apps')->where('id', $id)->find(); if (!$app) { return api_error('应用不存在', 404); } $secret = $this->customerService()->generateAppSecret(); Db::name('enterprise_customer_apps')->where('id', $id)->update([ 'app_secret_cipher' => $this->customerService()->encryptSecret($secret), 'secret_last4' => substr($secret, -4), 'updated_at' => date('Y-m-d H:i:s'), ]); $fresh = Db::name('enterprise_customer_apps')->where('id', $id)->find(); return api_success([ 'app' => $this->customerService()->formatApp($fresh), 'app_secret' => $secret, ], '应用 Secret 已重置,请立即复制保存'); } public function orders(Request $request) { $customerId = (int)$request->input('customer_id', 0); if ($customerId <= 0) { return api_error('客户 ID 不能为空', 422); } $rows = Db::name('enterprise_customer_order_refs') ->alias('r') ->leftJoin('orders o', 'o.id = r.order_id') ->leftJoin('order_products p', 'p.order_id = r.order_id') ->field([ 'r.id', 'r.customer_id', 'r.external_order_no', 'r.order_id', 'r.order_no', 'r.appraisal_no', 'r.created_at', 'o.order_status', 'o.display_status', 'o.pay_amount', 'p.product_name', ]) ->where('r.customer_id', $customerId) ->order('r.id', 'desc') ->select() ->toArray(); return api_success([ 'list' => array_map(static fn(array $item) => [ 'id' => (int)$item['id'], 'customer_id' => (int)$item['customer_id'], 'external_order_no' => (string)$item['external_order_no'], 'order_id' => (int)$item['order_id'], 'order_no' => (string)$item['order_no'], 'appraisal_no' => (string)$item['appraisal_no'], 'product_name' => (string)($item['product_name'] ?: '待完善物品信息'), 'order_status' => (string)($item['order_status'] ?? ''), 'display_status' => (string)($item['display_status'] ?? ''), 'pay_amount' => (float)($item['pay_amount'] ?? 0), 'created_at' => (string)$item['created_at'], ], $rows), ]); } public function events(Request $request) { $customerId = (int)$request->input('customer_id', 0); if ($customerId <= 0) { return api_error('客户 ID 不能为空', 422); } $rows = Db::name('enterprise_order_events') ->where('customer_id', $customerId) ->order('id', 'desc') ->limit(200) ->select() ->toArray(); return api_success([ 'list' => array_map(fn(array $item) => $this->webhookService()->formatEvent($item), $rows), ]); } public function deliveries(Request $request) { $customerId = (int)$request->input('customer_id', 0); $eventId = (int)$request->input('event_id', 0); if ($customerId <= 0 && $eventId <= 0) { return api_error('客户 ID 或事件 ID 不能为空', 422); } $query = Db::name('enterprise_webhook_deliveries')->order('id', 'desc')->limit(200); if ($customerId > 0) { $query->where('customer_id', $customerId); } if ($eventId > 0) { $query->where('event_id', $eventId); } $rows = $query->select()->toArray(); return api_success([ 'list' => array_map(fn(array $item) => $this->webhookService()->formatDelivery($item), $rows), ]); } public function resendEvent(Request $request) { $eventId = (int)$request->input('event_id', 0); if ($eventId <= 0) { return api_error('事件 ID 不能为空', 422); } try { $result = $this->webhookService()->deliverEvent($eventId, true); } catch (\RuntimeException $e) { return api_error($e->getMessage(), 422); } catch (\Throwable $e) { return api_error('事件补发失败', 500, [ 'detail' => $e->getMessage(), ]); } return api_success([ 'delivery' => $this->webhookService()->formatDelivery($result['delivery']), 'sent' => (bool)$result['sent'], ], $result['sent'] ? '事件已补发成功' : '事件补发未成功,请查看推送记录'); } public function orderProgress(Request $request) { $customerId = (int)$request->input('customer_id', 0); $externalOrderNo = trim((string)$request->input('external_order_no', '')); if ($customerId <= 0 || $externalOrderNo === '') { return api_error('客户 ID 和外部订单号不能为空', 422); } $customer = Db::name('enterprise_customers')->where('id', $customerId)->find(); if (!$customer) { return api_error('客户不存在', 404); } try { $order = (new EnterpriseOrderService())->findOrder($customer, $externalOrderNo, ''); } catch (\Throwable $e) { return api_error($e->getMessage(), 404); } return api_success([ 'order' => $order, ]); } private function countMap(string $table, array $customerIds): array { if (!$customerIds) { return []; } $rows = Db::name($table) ->field('customer_id, COUNT(*) AS total') ->whereIn('customer_id', $customerIds) ->group('customer_id') ->select() ->toArray(); $map = []; foreach ($rows as $row) { $map[(int)$row['customer_id']] = (int)$row['total']; } return $map; } private function customerService(): EnterpriseCustomerService { return new EnterpriseCustomerService(); } private function webhookService(): EnterpriseWebhookService { return new EnterpriseWebhookService(); } }