HEX
Server: nginx/1.18.0
System: Linux test-ipsremont 5.4.0-214-generic #234-Ubuntu SMP Fri Mar 14 23:50:27 UTC 2025 x86_64
User: ips (1000)
PHP: 8.0.30
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/quadcode/app/Console/Commands/PipedriveWhatsAppSender.php
<?php

namespace App\Console\Commands;

use App\Helpers\PipedriveHelper;
use App\Models\PipedriveQueue;
use App\Models\PipedriveUser;
use App\Repository\PipedriveQueueRepository;
use App\Repository\PipedriveUserRepository;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Command\Command as CommandAlias;
use Throwable;
use Twilio\Rest\Client;

class PipedriveWhatsAppSender extends Command
{

    protected $signature = 'pipedrive:whats-app-send';

    protected $description = 'Process pipedrive WhatsApp queue';

    private string $token;
    private string $error = '';

    private PipedriveQueueRepository $pipedriveQueueRepository;
    private PipedriveUserRepository $pipedriveUserRepository;

    private PipedriveHelper $pipedriveHelper;
    private Client $twilio;
    private string $fromPhone;

    private array $messages = [
        1 => [
            'pt' => <<<EOT
Olá {{1}} 👋

Sou {{2}} da Quadcode, seguindo sua solicitação deixada em nosso site. Um breve lembrete: você pode criar sua própria corretora de marca branca personalizada conosco.

*A cereja do bolo?* 🍒
Nós gerenciamos 90% dos processos, mesmo após o lançamento, para que você possa se concentrar na aquisição de clientes e geração de receita.

Poderia me informar onde seria conveniente para você discutir mais sobre isso?
EOT,
            'es' => <<<EOT
Hola {{1}} 👋

Soy {{2}} de Quadcode, dando seguimiento a la solicitud que dejaste en nuestro sitio web. Un breve recordatorio: puedes crear tu propia correduría de marca blanca personalizada con nosotros.

*¿La guinda del pastel?* 🍒
Nosotros nos encargamos del 90% de los procesos incluso después del lanzamiento para que tú te puedas concentrar en la adquisición de clientes y generación de ingresos.

¿Podrías indicarme dónde sería conveniente para ti discutir más al respecto?
EOT,
            'en' => <<<EOT
Hello {{1}} 👋

It’s {{2}} from Quadcode, following up on the request you left on our website. A brief reminder: you can create your own custom-made white-label brokerage with us.

*The cherry on top?* 🍒
We manage 90% of the processes even after the launch so that you can concentrate on customer acquisition and generating revenue.

Can you please let me know where it would be convenient for you to discuss further?
EOT,
        ],
        2 => [
            'pt' => <<<EOT
Olá {{1}},

Estou apenas verificando se você teve a chance de considerar minha proposta.

Vamos conversar aqui sobre como criar sua própria corretora. Alternativamente, podemos fazer uma ligação.
EOT,
            'es' => <<<EOT
Hola {{1}},

Solo quería comprobar si has tenido la oportunidad de considerar mi propuesta.

Podemos conversar aquí sobre cómo establecer tu propia correduría. Alternativamente, podemos hacer una llamada.
EOT,
            'en' => <<<EOT
Hello {{1}},

Just checking in to see if you had a chance to consider my proposal. 

Let's chat here about setting up your own brokerage. Alternatively, we can jump on a call.
EOT,
        ],
        3 => [
            'pt' => <<<EOT
Olá {{1}},

Retomando nosso bate-papo. Aqui está um resumo rápido caso tenha perdido minhas mensagens anteriores: Quadcode permite que você personalize e construa sua própria corretora em apenas 6 semanas. Cuidamos de todas as operações, permitindo que você se concentre em marketing e maximização de lucros.

Vamos falar sobre o bônus exclusivo que oferecemos e discutir as questões que você possa ter.

Quando podemos marcar uma breve reunião?

P.S. Se eu não receber notícias suas esta semana, vou assumir que não está interessado por agora.
EOT,
            'es' => <<<EOT
Hola {{1}},

Volviendo a nuestro chat. Aquí hay un rápido resumen en caso de que hayas perdido mis mensajes anteriores: Quadcode te permite personalizar y construir tu propia correduría en solo 6 semanas. Nosotros nos encargamos de todas las operaciones, permitiéndote concentrarte en hacer marketing y maximizar las ganancias.

Hablemos del bono exclusivo que ofrecemos y discutamos las preguntas que puedas tener.

¿Cuándo podríamos organizar una breve reunión?

P.D. Si no recibo noticias tuyas esta semana, asumiré que no estás interesado por ahora.
EOT,
            'en' => <<<EOT
Hello {{1}},

Circling back to our chat. Here’s a quick recap in case you missed my earlier messages:
Quadcode enables you to custom-build your own brokerage in just 6 weeks. We cover all the operations, letting you concentrate on doing marketing and maximizing profits.

Let’s talk about the exclusive bonus we offer and discuss questions you might have.

When can we arrange a brief meeting?

P.S. If I don’t hear back this week, I’ll assume it’s a pass for now.
EOT,
        ],
    ];

    public function __construct(PipedriveQueueRepository $pipedriveQueueRepository, PipedriveHelper $pipedriveHelper, PipedriveUserRepository $pipedriveUserRepository)
    {
        parent::__construct();

        $this->pipedriveQueueRepository = $pipedriveQueueRepository;
        $this->pipedriveUserRepository = $pipedriveUserRepository;

        $this->token = env('PIPEDRIVE_TOKEN');
        if (!empty($this->token)) {
            $this->pipedriveHelper = $pipedriveHelper;
        }

        $sid = env('TWILIO_ACCOUNT_SID');
        $token = env('TWILIO_AUTH_TOKEN');
        $fromPhone = env('TWILIO_FROM_PHONE');
        if (empty($sid) || empty($token) || empty($fromPhone)) {
            $this->error = 'TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN or TWILIO_FROM_PHONE need to set';
        } else {
            $this->twilio = new Client($sid, $token);
            $this->fromPhone = $fromPhone;
        }
    }

    private function getDataForMassage(array $data): array
    {
        $language = $data['lang'] ?? 'en';
        $phone = str_replace('+', '', $data['phone']);
        $name = $data['first_name'];

        return [$language, $phone, $name];
    }

    private function sendMessage(string $phone, string $message, array &$log): void
    {
        $message = $this->twilio->messages->create('whatsapp:+' . $phone, ['from' => 'whatsapp:+' . $this->fromPhone, 'body' => $message]);
        $log['messageSid'] = $message->sid;
    }

    private function selectLang(string $phone): string
    {
        if (preg_match('/^(55|244|238|351)/', $phone)) {
            return 'pt';
        }

        if (preg_match('/^(51|52|53|54|56|57|50|58|59|34)/', $phone)) {
            return 'es';
        }

        return 'en';
    }

    private function getMessage(int $step, string $phone, string $name, ?PipedriveUser $pipedriveUser = null): string
    {
        $language = $this->selectLang($phone);
        $message = $this->messages[$step][$language];


        if (empty($pipedriveUser)) {
            $userFirstName = 'Peter';
        } else {
            $userFirstName = explode(' ', $pipedriveUser->name)[0];
        }

        return str_replace(['{{1}}', '{{2}}'], [$name, $userFirstName], $message);
    }

    private function getPipedriveUser(int $userExternalId, PipedriveQueue $pipedriveQueueItem): ?PipedriveUser
    {
        $pipedriveUser = $this->pipedriveUserRepository->getByExternalId($userExternalId);
        if (empty($pipedriveUser)) {
            $response = $this->pipedriveHelper->getUser($userExternalId);
            if (!$response['success']) {
                $this->error($response['error']);
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_ERROR;
                $pipedriveQueueItem->save();

                return null;
            }
            $user = $response['data'];
            $pipedriveUser = new PipedriveUser(['external_id' => $user['id'], 'name' => $user['name']]);
            $pipedriveUser->save();
        }

        return $pipedriveUser;
    }

    private function firstStep(): void
    {
        $pipedriveQueue = $this->pipedriveQueueRepository->getFirstStepLeads();
        $this->line('Found first step items: ' . $pipedriveQueue->count());
        foreach ($pipedriveQueue as $pipedriveQueueItem) {
            /** @var PipedriveQueue $pipedriveQueueItem */
            $data = json_decode($pipedriveQueueItem->data, true);
            if (empty($data['referrer']) || in_array($data['referrer'], ['/saas', '/lp/saas_request'])) {
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_CANCEL;
                $pipedriveQueueItem->save();
                continue;
            }

            $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_IN_PROGRESS;
            $pipedriveQueueItem->save();

            $response = $this->pipedriveHelper->getLead($pipedriveQueueItem->lead_id);
            if (!$response['success']) {
                $this->error($response['error']);
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_ERROR;
                $pipedriveQueueItem->save();

                continue;
            }
            $lead = $response['data'];

            $userExternalId = $lead['owner_id'];

            $pipedriveUser = $this->getPipedriveUser($userExternalId, $pipedriveQueueItem);
            if (is_null($pipedriveUser)) {
                continue;
            }

            [$language, $phone, $name] = $this->getDataForMassage($data);

            $message = $this->getMessage(1, $phone, $name, $pipedriveUser);

            try {
                $log = ['from' => $this->fromPhone, 'to' => $phone, 'message' => $message];
                $this->sendMessage($phone, $message, $log);
            } catch (Throwable $exception) {
                $error = $exception->getMessage();
                $this->error($error);
                Log::channel('whatsAppJob')->error('Send message error: ' . $error, $log);
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_ERROR;
                $pipedriveQueueItem->save();

                continue;
            }
            Log::channel('whatsAppJob')->info('Send message', $log);

            $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_NEW;
            $pipedriveQueueItem->step = 2;
            $pipedriveQueueItem->save();
        }
    }

    private function secondStep(): void
    {
        $pipedriveQueue = $this->pipedriveQueueRepository->getSecondStepLeads();
        $this->line('Found second step items: ' . $pipedriveQueue->count());
        foreach ($pipedriveQueue as $pipedriveQueueItem) {
            /** @var PipedriveQueue $pipedriveQueueItem */
            $data = json_decode($pipedriveQueueItem->data, true);

            $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_IN_PROGRESS;
            $pipedriveQueueItem->save();

            $response = $this->pipedriveHelper->getLead($pipedriveQueueItem->lead_id);
            if (!$response['success']) {
                $this->error($response['error']);
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_ERROR;
                $pipedriveQueueItem->save();

                continue;
            }
            $lead = $response['data'];

            if ($lead['is_archived']) {
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_DONE;
                $pipedriveQueueItem->save();

                continue;
            }

            [$language, $phone, $name] = $this->getDataForMassage($data);

            $message = $this->getMessage(2, $phone, $name);

            try {
                $log = ['from' => $this->fromPhone, 'to' => $phone, 'message' => $message];
                $this->sendMessage($phone, $message, $log);
            } catch (Throwable $exception) {
                $error = $exception->getMessage();
                $this->error($error);
                Log::channel('whatsAppJob')->error('Send message error: ' . $error, $log);
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_ERROR;
                $pipedriveQueueItem->save();

                continue;
            }
            Log::channel('whatsAppJob')->info('Send message', $log);

            $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_NEW;
            $pipedriveQueueItem->step = 3;
            $pipedriveQueueItem->save();
        }
    }

    private function thirdStep(): void
    {
        $pipedriveQueue = $this->pipedriveQueueRepository->getThirdStepLeads();
        $this->line('Found third step items: ' . $pipedriveQueue->count());
        foreach ($pipedriveQueue as $pipedriveQueueItem) {
            /** @var PipedriveQueue $pipedriveQueueItem */
            $data = json_decode($pipedriveQueueItem->data, true);

            $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_IN_PROGRESS;
            $pipedriveQueueItem->save();

            $response = $this->pipedriveHelper->getLead($pipedriveQueueItem->lead_id);
            if (!$response['success']) {
                $this->error($response['error']);
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_ERROR;
                $pipedriveQueueItem->save();

                continue;
            }
            $lead = $response['data'];

            if ($lead['is_archived']) {
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_DONE;
                $pipedriveQueueItem->save();

                continue;
            }

            [$language, $phone, $name] = $this->getDataForMassage($data);

            $message = $this->getMessage(3, $phone, $name);

            try {
                $log = ['from' => $this->fromPhone, 'to' => $phone, 'message' => $message];
                $this->sendMessage($phone, $message, $log);
            } catch (Throwable $exception) {
                $error = $exception->getMessage();
                $this->error($error);
                Log::channel('whatsAppJob')->error('Send message error: ' . $error, $log);
                $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_ERROR;
                $pipedriveQueueItem->save();

                continue;
            }
            Log::channel('whatsAppJob')->info('Send message', $log);

            $pipedriveQueueItem->whats_app_status = PipedriveQueue::STATUS_DONE;
            $pipedriveQueueItem->step = 3;
            $pipedriveQueueItem->save();
        }
    }

    public function handle(): int
    {
        if (empty($this->token)) {
            $this->error('Need to fill PIPEDRIVE_TOKEN in .env');
        }
        if (!empty($this->error)) {
            $this->error($this->error);
        }

        $this->info('Send WhatsApp messages');

        $this->thirdStep();
        $this->secondStep();
        $this->firstStep();

        $this->info(PHP_EOL . 'Done');

        return CommandAlias::SUCCESS;
    }

}