sessionService->createSession($user, $native); return [ 'session_id' => $verification->session_id, 'verification_url' => $this->sessionService->verificationUrl($verification), 'status' => $verification->didit_status, 'attempt' => $verification->attempt, 'attempts_left' => $this->attemptsLeft($this->profileFor($user)), ]; } public function statusFor(User $user): array { $profile = $this->profileFor($user); $verification = $user->latestIdentityVerification()->first(); return [ 'identity_verification_status' => $profile?->identity_verification_status?->value ?? IdentityVerificationStatusEnum::NOT_STARTED->value, 'identity_verified_at' => $profile?->identity_verified_at, 'required' => (bool) ($profile?->identity_verification_required ?? false), 'attempts_left' => $this->attemptsLeft($profile), 'can_retry' => $this->canRetry($profile), 'has_open_session' => $verification !== null && in_array($verification->didit_status, ['Not Started', 'In Progress'], true), 'last_verification' => $verification ? new IdentityVerificationResource($verification) : null, ]; } public function pending(int $perPage, int $page): array { $paginator = IdentityVerification::query() ->with('user') ->whereIn('didit_status', ['In Review', 'Declined']) ->orderBy('completed_at') ->paginate(perPage: $perPage, page: $page); return [ 'data' => IdentityVerificationResource::collection($paginator->items()), 'total' => $paginator->total(), 'from' => $paginator->firstItem(), 'to' => $paginator->lastItem(), ]; } /** * Busca o laudo com as imagens frescas: as URLs do Didit sao presignadas e de * vida curta, por isso nao sao persistidas e sim buscadas na hora de exibir. */ public function findWithFreshDecision(int $id): ?array { $verification = IdentityVerification::with('user')->find($id); if (! $verification) { return null; } $decision = null; try { $decision = $this->sessionService->getDecision($verification->session_id); } catch (\Throwable) { // Laudo persistido ja basta para a decisao; so as imagens ficam indisponiveis. } return [ 'verification' => new IdentityVerificationResource($verification), 'decision' => $decision ?? $verification->decision, 'decision_fresh' => $decision !== null, ]; } public function review(int $id, bool $approved, User $reviewer, ?string $comment): IdentityVerification { $verification = IdentityVerification::with('user')->find($id); if (! $verification) { throw new RuntimeException(__('identity_verification.not_found')); } $user = $verification->user; $profile = $user ? $this->profileFor($user) : null; if (! $profile) { throw new RuntimeException(__('identity_verification.profile_missing')); } DB::transaction(function () use ($verification, $profile, $approved, $reviewer, $comment) { $verification->forceFill([ 'reviewed_by' => $reviewer->id, 'reviewed_at' => now(), 'review_comment' => $comment, ])->save(); $profile->forceFill([ 'identity_verification_status' => $approved ? IdentityVerificationStatusEnum::APPROVED->value : IdentityVerificationStatusEnum::DECLINED->value, 'identity_verified_at' => $approved ? now() : null, ]); if ($approved && $profile instanceof Provider) { $profile->document_verified = true; } $profile->save(); }); if ($approved && $profile instanceof Provider) { $this->providerService->approve($profile->id); } return $verification->refresh(); } // private function attemptsLeft(?Model $profile): int { $max = (int) config('services.didit.max_attempts', 3); return max(0, $max - (int) ($profile?->identity_verification_attempts ?? 0)); } private function canRetry(?Model $profile): bool { if (! $profile) { return false; } $status = $profile->identity_verification_status; return $status instanceof IdentityVerificationStatusEnum && $status->allowsNewAttempt() && $this->attemptsLeft($profile) > 0; } private function profileFor(User $user): Provider|Client|null { return $user->type === UserTypeEnum::PROVIDER ? $user->provider : $user->client; } }