summaryAICard->assertValidCallbackSignature($request); if (trim((string) $request->input('type', '')) !== 'actionCallback') { return response()->json(['success' => false, 'message' => '不支持的钉钉回调类型。'], Response::HTTP_UNPROCESSABLE_ENTITY); } $outTrackId = trim((string) $request->input('outTrackId', '')); $companyId = $this->confirmations->companyIdFromOutTrackId($outTrackId); if ($companyId === null) { return response()->json(['success' => false, 'message' => '确认卡片标识无效。'], Response::HTTP_NOT_FOUND); } $dingTalkUserId = trim((string) $request->input('userId', '')); if ($dingTalkUserId === '') { return response()->json(['success' => false, 'message' => '钉钉确认人缺失。'], Response::HTTP_UNPROCESSABLE_ENTITY); } $content = $request->input('content', []); if (is_string($content)) { $decoded = json_decode($content, true); $content = is_array($decoded) ? $decoded : []; } $params = is_array($content) ? data_get($content, 'cardPrivateData.params', []) : []; if (is_string($params)) { $decoded = json_decode($params, true); $params = is_array($decoded) ? $decoded : []; } if (! is_array($params) || trim((string) ($params['action'] ?? '')) !== 'confirm') { return response()->json(['success' => false, 'message' => '不支持的卡片操作。'], Response::HTTP_UNPROCESSABLE_ENTITY); } $record = $this->confirmations->find($outTrackId, $companyId); if ($record === null) { return response()->json(['success' => false, 'message' => '确认记录不存在。'], Response::HTTP_NOT_FOUND); } $result = $this->confirmations->confirm( $outTrackId, $companyId, $dingTalkUserId, trim((string) $request->input('corpId', '')), ); // 重复回调也使用同一个 outUpdateId 重试卡片状态,避免首次回写卡片 // 短暂失败后按钮一直停留在“厂家确认”,财务确认记录本身保持幂等。 try { $this->summaryAICard->updateConfirmed($result['record']); } catch (Throwable $exception) { report($exception); } return response()->json([ 'success' => true, 'confirmed' => true, 'newly_confirmed' => $result['newly_confirmed'], 'confirmed_at' => (string) ($result['record']['confirmed_at'] ?? ''), ]); } }