validate(). platform парсится по тому же * правилу: B[123]_ → B1/B2/B3, иначе DIRECT. * * Используется исключительно в тест-инфраструктуре (Tests-namespace). * * Task 2 — Phase 1 Portal Client Imitation Harness. * Spec: docs/superpowers/specs/2026-06-03-portal-client-imitation-phase1-design.md */ final class LeadInjector { /** * Инъектировать заявку с сигналом «сайт». * * @param string $domain Домен сигнала (например, 'vashinvestor.ru'). * Составляет identifier в project-поле: "{$platform}_{$domain}". * @param string $phone Телефон в формате 7XXXXXXXXXX. * @param string|null $tag Тег региона (например, 'Москва'); может быть null. * @param string $platform Префикс платформы: 'B1', 'B2', 'B3' или иное (→ DIRECT). * @param int|null $vid Внешний ID заявки поставщика. Если null — генерируется уникальный. */ public function site( string $domain, string $phone, ?string $tag = null, string $platform = 'B1', ?int $vid = null, ): SupplierLead { $project = "{$platform}_{$domain}"; return $this->inject($project, $phone, $tag, $platform, $vid); } /** * Инъектировать заявку с сигналом «звонок». * * @param string $number Номер телефона для call-сигнала (7XXXXXXXXXX). * Составляет identifier в project-поле: "{$platform}_{$number}". * @param string $phone Телефон звонящего в формате 7XXXXXXXXXX. * @param string|null $tag Тег региона; может быть null. * @param string $platform Префикс платформы: 'B1', 'B2', 'B3' или иное (→ DIRECT). * @param int|null $vid Внешний ID заявки поставщика. Если null — генерируется уникальный. */ public function call( string $number, string $phone, ?string $tag = null, string $platform = 'B1', ?int $vid = null, ): SupplierLead { $project = "{$platform}_{$number}"; return $this->inject($project, $phone, $tag, $platform, $vid); } /** * Общий внутренний метод создания SupplierLead + синхронный dispatch Job. * * raw_payload содержит ровно те ключи, что SupplierWebhookController кладёт * из $request->validate(): vid, project, phone, time, tag (null → не добавляем, * чтобы не нарушить nullable-контракт RouteSupplierLeadJob). * * platform парсится по тому же правилу, что Controller::parsePlatform(): * /^(B[123])_/ → B1/B2/B3, иначе DIRECT. */ private function inject( string $project, string $phone, ?string $tag, string $platform, ?int $vid, ): SupplierLead { $resolvedVid = $vid ?? $this->generateVid(); $parsedPlatform = $this->parsePlatform($project); $rawPayload = [ 'vid' => $resolvedVid, 'project' => $project, 'phone' => $phone, 'time' => time(), ]; if ($tag !== null) { $rawPayload['tag'] = $tag; } $lead = SupplierLead::create([ 'platform' => $parsedPlatform, 'raw_payload' => $rawPayload, 'vid' => $resolvedVid, 'phone' => $phone, 'received_at' => now(), 'source' => 'webhook', ]); RouteSupplierLeadJob::dispatchSync($lead->id); return $lead->refresh(); } /** * Парсит platform из project-поля — идентично Controller::parsePlatform(). * B[123]_ → B1/B2/B3, иначе DIRECT. */ private function parsePlatform(string $project): string { if (preg_match('/^(B[123])_/', $project, $m) === 1) { return $m[1]; } return 'DIRECT'; } /** * Генерирует уникальный vid для использования, когда явный vid не передан. * Диапазон 1_000_000_000..9_999_999_999 — вне реальных vid поставщика (< 10^9). */ private function generateVid(): int { return random_int(1_000_000_000, 9_999_999_999); } }