where('order_id', $orderId)->find(); if (!$ref) { return null; } $customer = Db::name('enterprise_customers')->where('id', (int)$ref['customer_id'])->find(); $order = Db::name('orders')->where('id', $orderId)->find(); if (!$customer || !$order) { return null; } $eventMeta = $this->eventMeta($eventCode); $payload = [ 'event_code' => $eventCode, 'event_text' => $eventMeta['event_text'], 'customer_id' => (string)$customer['customer_code'], 'customer_code' => (string)$customer['customer_code'], 'external_order_no' => (string)$ref['external_order_no'], 'order_no' => (string)$order['order_no'], 'appraisal_no' => (string)$order['appraisal_no'], 'status_code' => $eventMeta['status_code'], 'status_text' => $eventMeta['status_text'], 'occurred_at' => date('Y-m-d H:i:s'), 'data' => $data, ]; $eventId = (int)Db::name('enterprise_order_events')->insertGetId([ 'customer_id' => (int)$customer['id'], 'order_id' => $orderId, 'external_order_no' => (string)$ref['external_order_no'], 'event_code' => $eventCode, 'event_text' => $eventMeta['event_text'], 'status_code' => $eventMeta['status_code'], 'status_text' => $eventMeta['status_text'], 'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE), 'occurred_at' => $payload['occurred_at'], 'created_at' => date('Y-m-d H:i:s'), ]); $payload['event_id'] = $eventId; if ($sendNow) { $this->deliverEvent($eventId, false); } return $payload; } public function deliverEvent(int $eventId, bool $manual = false): array { $event = Db::name('enterprise_order_events')->where('id', $eventId)->find(); if (!$event) { throw new \RuntimeException('事件不存在'); } $customer = Db::name('enterprise_customers')->where('id', (int)$event['customer_id'])->find(); if (!$customer) { throw new \RuntimeException('客户不存在'); } $url = trim((string)($customer['webhook_url'] ?? '')); $appKey = (string)Db::name('enterprise_customer_apps') ->where('customer_id', (int)$customer['id']) ->where('status', 'enabled') ->order('id', 'asc') ->value('app_key'); $attemptNo = (int)Db::name('enterprise_webhook_deliveries')->where('event_id', $eventId)->count() + 1; if (!(bool)($customer['webhook_enabled'] ?? false) || $url === '' || $appKey === '') { $delivery = $this->saveDelivery($event, $url, $appKey, $attemptNo, 'skipped', 0, '', 'Webhook未启用或配置不完整', $manual); return ['delivery' => $delivery, 'sent' => false]; } $payload = json_decode((string)$event['payload_json'], true); if (!is_array($payload)) { $payload = []; } $payload['event_id'] = $eventId; $result = $this->postJson($url, $payload, $appKey); $status = ($result['http_status'] >= 200 && $result['http_status'] < 300 && $result['error_message'] === '') ? 'success' : 'failed'; $delivery = $this->saveDelivery( $event, $url, $appKey, $attemptNo, $status, $result['http_status'], $result['response_body'], $result['error_message'], $manual ); return ['delivery' => $delivery, 'sent' => $status === 'success']; } public function formatEvent(array $item): array { return [ 'id' => (int)$item['id'], 'customer_id' => (int)$item['customer_id'], 'order_id' => (int)$item['order_id'], 'external_order_no' => (string)$item['external_order_no'], 'event_code' => (string)$item['event_code'], 'event_text' => (string)$item['event_text'], 'status_code' => (string)$item['status_code'], 'status_text' => (string)$item['status_text'], 'occurred_at' => (string)$item['occurred_at'], 'created_at' => (string)$item['created_at'], ]; } public function formatDelivery(array $item): array { return [ 'id' => (int)$item['id'], 'event_id' => (int)$item['event_id'], 'customer_id' => (int)$item['customer_id'], 'webhook_url' => (string)$item['webhook_url'], 'app_key' => (string)$item['app_key'], 'attempt_no' => (int)$item['attempt_no'], 'delivery_status' => (string)$item['delivery_status'], 'delivery_status_text' => $this->deliveryStatusText((string)$item['delivery_status']), 'http_status' => (int)$item['http_status'], 'response_body' => (string)($item['response_body'] ?? ''), 'error_message' => (string)($item['error_message'] ?? ''), 'is_manual' => (bool)($item['is_manual'] ?? false), 'sent_at' => (string)($item['sent_at'] ?? ''), 'created_at' => (string)($item['created_at'] ?? ''), ]; } private function eventMeta(string $eventCode): array { return match ($eventCode) { 'order_created' => ['event_text' => '订单创建', 'status_code' => 'pending_shipping', 'status_text' => '待寄送商品'], 'inbound_received' => ['event_text' => '快递已到仓', 'status_code' => 'received', 'status_text' => '鉴定中心已收货'], 'appraising' => ['event_text' => '物品鉴定中', 'status_code' => 'appraising', 'status_text' => '物品鉴定中'], 'appraisal_finished' => ['event_text' => '物品鉴定完成', 'status_code' => 'generating_report', 'status_text' => '物品鉴定完成'], 'report_published' => ['event_text' => '报告已发布', 'status_code' => 'report_published', 'status_text' => '报告已发布'], 'return_shipped' => ['event_text' => '物品已寄回', 'status_code' => 'return_shipped', 'status_text' => '物品已寄回'], 'completed' => ['event_text' => '订单已完成', 'status_code' => 'completed', 'status_text' => '已完成'], 'supplement_required' => ['event_text' => '需要补充资料', 'status_code' => 'pending_supplement', 'status_text' => '需要补充资料'], default => ['event_text' => $eventCode, 'status_code' => $eventCode, 'status_text' => $eventCode], }; } private function postJson(string $url, array $payload, string $appKey): array { $body = json_encode($payload, JSON_UNESCAPED_UNICODE); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_TIMEOUT => 6, CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'X-AXY-App-Key: ' . $appKey, ], ]); $response = curl_exec($ch); $errno = curl_errno($ch); $error = curl_error($ch); $httpStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return [ 'http_status' => $httpStatus, 'response_body' => is_string($response) ? substr($response, 0, 2000) : '', 'error_message' => $errno ? $error : '', ]; } private function saveDelivery( array $event, string $url, string $appKey, int $attemptNo, string $status, int $httpStatus, string $responseBody, string $errorMessage, bool $manual ): array { $now = date('Y-m-d H:i:s'); $id = (int)Db::name('enterprise_webhook_deliveries')->insertGetId([ 'event_id' => (int)$event['id'], 'customer_id' => (int)$event['customer_id'], 'webhook_url' => $url, 'app_key' => $appKey, 'attempt_no' => $attemptNo, 'delivery_status' => $status, 'http_status' => $httpStatus, 'response_body' => $responseBody, 'error_message' => $errorMessage, 'is_manual' => $manual ? 1 : 0, 'sent_at' => $status === 'skipped' ? null : $now, 'created_at' => $now, 'updated_at' => $now, ]); return Db::name('enterprise_webhook_deliveries')->where('id', $id)->find() ?: []; } private function deliveryStatusText(string $status): string { return match ($status) { 'success' => '推送成功', 'failed' => '推送失败', 'skipped' => '已跳过', default => $status, }; } }