isConfigured()) { return response()->json(['code' => '503', 'msg' => 'not configured'], 503); } $appId = $configuration->appId(); $appSecret = $configuration->appSecret(); $timestamp = trim((string) $request->header('X-Tsign-Open-TIMESTAMP')); $algorithm = trim((string) $request->header('X-Tsign-Open-SIGNATURE-ALGORITHM')); $callbackAppId = trim((string) $request->header('X-Tsign-Open-App-Id')); $signature = strtolower(trim((string) $request->header('X-Tsign-Open-SIGNATURE'))); $rawBody = $request->getContent(); if (! ctype_digit($timestamp) || strcasecmp($algorithm, 'HmacSHA256') !== 0 || ! hash_equals($appId, $callbackAppId) || $signature === '' || ! $this->timestampIsFresh($timestamp)) { return response()->json(['code' => '401', 'msg' => 'invalid callback'], 401); } $queryValues = $request->query(); ksort($queryValues, SORT_STRING); $data = $timestamp . collect($queryValues)->map(fn ($value): string => $this->queryValue($value))->implode('') . $rawBody; $expected = strtolower(hash_hmac('sha256', $data, $appSecret)); if (! hash_equals($expected, $signature)) { return response()->json(['code' => '401', 'msg' => 'invalid callback'], 401); } $payload = json_decode($rawBody, true); if (! is_array($payload)) { return response()->json(['code' => '400', 'msg' => 'invalid json'], 400); } if (($payload['action'] ?? null) !== 'SIGN_FLOW_COMPLETE') { return $this->success(); } $signFlowId = trim((string) ($payload['signFlowId'] ?? '')); $remoteStatus = trim((string) ($payload['signFlowStatus'] ?? '')); if ($signFlowId === '' || $remoteStatus === '') { return $this->success(); } $payloadHash = hash('sha256', $rawBody); $signatureRecord = $service->acceptCallback($signFlowId, $remoteStatus, $payloadHash); if ($signatureRecord === null) { $agreementRecord = $agreementService->acceptCallback($signFlowId, $remoteStatus, $payloadHash, [ 'event_key' => hash('sha256', $timestamp . '|' . ($payload['action'] ?? '') . '|' . $signFlowId . '|' . $payloadHash), 'action' => (string) ($payload['action'] ?? 'SIGN_FLOW_COMPLETE'), 'timestamp' => $timestamp, 'raw_body' => $rawBody, ]); if ($agreementRecord && $remoteStatus === '2' && $agreementService->autoArchive($agreementRecord) && $agreementRecord->status !== CourierAgreementSignature::STATUS_ARCHIVED) { ArchiveCourierAgreementSignature::dispatch($agreementRecord->id); } } if ($signatureRecord && $remoteStatus === '2' && $signatureRecord->status !== CourierQuoteSignature::STATUS_ARCHIVED) { ArchiveCourierQuoteSignature::dispatchAfterResponse($signatureRecord->id); } return $this->success(); } private function timestampIsFresh(string $timestamp): bool { $seconds = strlen($timestamp) >= 13 ? intdiv((int) $timestamp, 1000) : (int) $timestamp; $tolerance = EsignConfiguration::fromConfig()->callbackToleranceSeconds(); return abs(now()->timestamp - $seconds) <= $tolerance; } private function queryValue(mixed $value): string { if (is_array($value)) { return collect($value)->map(fn ($item): string => $this->queryValue($item))->implode(''); } return is_scalar($value) ? (string) $value : ''; } private function success(): JsonResponse { return response()->json(['code' => '200', 'msg' => 'success']); } }