request->header(); $accessAppkey = $headers['access-appkey'] ?? ''; $accessNonce = $headers['access-nonce'] ?? ''; $accessTimestamp = $headers['access-timestamp'] ?? ''; $accessSign = $headers['access-sign'] ?? ''; // 记录回调请求 Log::info('Gancao callback received', [ 'headers' => [ 'access-appkey' => $accessAppkey, 'access-nonce' => $accessNonce, 'access-timestamp' => $accessTimestamp, 'access-sign' => $accessSign, ], 'body' => $rawBody, ]); // 验证签名 if (!$this->verifySign($accessAppkey, $accessNonce, $accessTimestamp, $accessSign, $rawBody)) { Log::warning('Gancao callback sign verification failed', [ 'access-appkey' => $accessAppkey, 'access-sign' => $accessSign, ]); return $this->response('sign verification failed', 403); } // 解析回调数据 $data = json_decode($rawBody, true); if (!is_array($data)) { Log::error('Gancao callback invalid json', ['body' => $rawBody]); return $this->response('invalid json', 400); } // 处理回调数据 $this->handleCallback($data); // 必须在5秒内返回 "ok",否则会认为回调失败 return $this->response('ok'); } catch (\Throwable $e) { Log::error('Gancao callback exception', [ 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), ]); // 即使出错也要返回 ok,避免甘草重试 return $this->response('ok'); } } /** * 验证回调签名 * * 签名算法:md5(access-appkey + secret-key + access-nonce + access-timestamp + $sBody) * * @param string $appkey * @param string $nonce * @param string $timestamp * @param string $sign * @param string $body * @return bool */ private function verifySign(string $appkey, string $nonce, string $timestamp, string $sign, string $body): bool { $config = Config::get('gancao_scm', []); // 获取配置的 appkey 和 secret-key $configAppkey = (string) ($config['callback_appkey'] ?? $config['biz_ak'] ?? ''); $secretKey = (string) ($config['callback_secret_key'] ?? $config['biz_sk'] ?? ''); // 验证 appkey if ($appkey !== $configAppkey) { Log::warning('Gancao callback appkey mismatch', [ 'received' => $appkey, 'expected' => $configAppkey, ]); return false; } // 计算签名 $expectedSign = md5($appkey . $secretKey . $nonce . $timestamp . $body); // 验证签名 if ($sign !== $expectedSign) { Log::warning('Gancao callback sign mismatch', [ 'received' => $sign, 'expected' => $expectedSign, ]); return false; } return true; } /** * 处理回调数据 * * @param array $data * @return void */ private function handleCallback(array $data): void { $recipelOrderNo = (string) ($data['recipel_order_no'] ?? ''); $state = (int) ($data['state'] ?? 0); $ext = $data['ext'] ?? []; if ($recipelOrderNo === '') { Log::warning('Gancao callback missing recipel_order_no', ['data' => $data]); return; } // 查找订单 $order = PrescriptionOrder::where('gancao_reciperl_order_no', $recipelOrderNo) ->whereNull('delete_time') ->find(); if (!$order) { Log::warning('Gancao callback order not found', [ 'recipel_order_no' => $recipelOrderNo, ]); return; } // 更新订单状态 $this->updateOrderStatus($order, $state, $ext); // 记录日志 $this->writeCallbackLog($order, $state, $ext); Log::info('Gancao callback processed', [ 'order_id' => $order->id, 'recipel_order_no' => $recipelOrderNo, 'state' => $state, 'ext' => $ext, ]); } /** * 更新订单状态 * * 状态说明: * - 10: 系统审核中 * - 11: 系统审核通过 * - 110: 订单药房流转制作中 * - 20: 物流中 * - 30: 完成(终态) * - 90: 拦截(终止流转:可恢复) * - 91: 主动撤单(退费:终态) * - 92: 驳回(无法制作并退费:终态) * * @param PrescriptionOrder $order * @param int $state * @param array $ext * @return void */ private function updateOrderStatus(PrescriptionOrder $order, int $state, array $ext): void { // 保存甘草订单状态 $order->gancao_order_state = $state; // 根据状态更新订单履约状态 switch ($state) { case 10: // 系统审核中 case 11: // 系统审核通过 // 保持当前状态 break; case 110: // 订单药房流转制作中 $flowName = (string) ($ext['flow_name'] ?? ''); $supplier = (string) ($ext['supplier'] ?? ''); // 记录流程信息 $order->gancao_flow_name = mb_substr($flowName, 0, 100); $order->gancao_supplier = mb_substr($supplier, 0, 100); // 如果是发货流程,更新履约状态 if (str_contains($flowName, '发货') || str_contains($flowName, '寄出')) { if ((int) $order->fulfillment_status === 2) { $order->fulfillment_status = 5; // 已发货 } } break; case 20: // 物流中 $shippingName = (string) ($ext['shipping_name'] ?? ''); $nu = (string) ($ext['nu'] ?? ''); $supplier = (string) ($ext['supplier'] ?? ''); // 更新物流信息 if ($nu !== '') { $order->tracking_number = mb_substr($nu, 0, 100); } if ($shippingName !== '') { // 转换快递公司名称 $expressMap = [ '顺丰' => 'sf', '京东' => 'jd', '极兔' => 'jt', ]; foreach ($expressMap as $name => $code) { if (str_contains($shippingName, $name)) { $order->express_company = $code; break; } } } // 更新履约状态为已发货 if ((int) $order->fulfillment_status === 2) { $order->fulfillment_status = 5; } break; case 30: // 完成 // 更新履约状态为已完成 if ((int) $order->fulfillment_status !== 4) { $order->fulfillment_status = 3; } break; case 90: // 拦截 // 记录拦截原因 $order->gancao_remark = '订单被拦截'; break; case 91: // 主动撤单 // 更新履约状态为已取消 if ((int) $order->fulfillment_status !== 3) { $order->fulfillment_status = 4; } $order->gancao_remark = '主动撤单(已退费)'; break; case 92: // 驳回 // 更新履约状态为已取消 if ((int) $order->fulfillment_status !== 3) { $order->fulfillment_status = 4; } $order->gancao_remark = '订单被驳回(无法制作并退费)'; break; } try { $order->save(); } catch (\Throwable $e) { Log::error('Gancao callback update order failed', [ 'order_id' => $order->id, 'error' => $e->getMessage(), ]); } } /** * 记录回调日志 * * @param PrescriptionOrder $order * @param int $state * @param array $ext * @return void */ private function writeCallbackLog(PrescriptionOrder $order, int $state, array $ext): void { $stateMap = [ 10 => '系统审核中', 11 => '系统审核通过', 110 => '订单药房流转制作中', 20 => '物流中', 30 => '完成', 90 => '拦截', 91 => '主动撤单', 92 => '驳回', ]; $stateName = $stateMap[$state] ?? "状态{$state}"; $summary = "甘草订单状态更新:{$stateName}"; // 添加扩展信息 if (isset($ext['flow_name'])) { $summary .= ",流程:{$ext['flow_name']}"; } if (isset($ext['shipping_name']) && isset($ext['nu'])) { $summary .= ",物流:{$ext['shipping_name']} {$ext['nu']}"; } $log = new PrescriptionOrderLog(); $log->prescription_order_id = (int) $order->id; $log->admin_id = 0; // 系统回调 $log->admin_name = '甘草系统'; $log->action = 'gancao_callback'; $log->summary = mb_substr($summary, 0, 500); $log->create_time = time(); try { $log->save(); } catch (\Throwable $e) { // 忽略日志写入错误 } } /** * 返回响应 * * @param string $message * @param int $code * @return Response */ private function response(string $message, int $code = 200): Response { return response($message, $code, [], 'html'); } }