Отправка и получение SMS в Laravel

Это перевод первой части из серии статей написанных Michael Heap. Вторую часть вы можете найти тут.

Что нам понадобится

Вам понадобится аккаунт в Nexmo и установленный Nexmo Command Line tool чтобы вы могли повторить действия описанные в этой статье.

Во второй части статьи мы будем получать вебхук запросы от Nexmo при получении SMS, поэтому вам необходимо будет пробросить локальный порт, чтобы Nexmo мог до вас достучаться. Если вы не знаете как пробросить локальный порт, вы всегда можете использовать Ngrok, который облегчит вам задачу.

Начинаем

Подготовка проекта, генерация моделей, контроллеров, миграций довольно утомительное занятие. Чтобы сохранить вам немного времени, я подготовил "Quick Start" репозиторий, в котором все эти вещи уже сделаны.

Начните с того, что форкните Deskmo репозиторий в свой аккаунт, чтобы после внесения изменений вы могли запушить их обратно на Github. Затем склонируйте бранч initial-scaffolding к себе на компьютер. В этом бранче уже сгенерированы все необходимые модели, миграции и формы.

$ git clone -b initial-scaffolding https://github.com/{YOUR-USER}/deskmo
$ cd deskmo

После того, как вы склонировали репозиторий к себе на компьютер, переименуйте файл .env.example в .env и отредактируйте его под себя.

Далее выполните команду composer install чтобы установить все необходимые зависимости. В зависимостях нет ничего лишнего, это обычная установка Laravel, за исключением пакета laravelcollective/html с помощью которого мы будем выводить формы.

После того как composer установит все пакеты, мы сможем запустить наш проект. Нам осталось сгенерировать новый APP_KEY, применить миграции и запустить сервер:

$ php artisan key:generate
$ php artisan migrate
$ php artisan serve

Если все прошло успешно, вы можете перейти в браузере по адресу http://localhost:8000/register и зарегистрировать нового пользователя. Обратите внимание, что к стандартной регистрации добавлось новое поле "Phone". Позже мы будем отправлять смс на этот номер, так что убедитесь что вы ввели его правильно (включая международный код, например 79221234567).

После создания аккаунта, вас автоматически залогинит в систему, и отправит на страницу где будет написано "No tickets" (нет заявок). Нажмите на кнопку "New Ticket" (создать заявку) в правом верхнем углу.

Ваш "Recipient User ID" будет "1". В будущем, мы возможно добавим автокомплит для этого поля, но пока что просто введите в это поле 1.

Заполните форму и нажмите на кнопку "Submit". Это создаст заявку и отправит вас обратно на страницу со списком всех заявок. На этот раз в списке должна появиться ваша новая заявка.

Мы почти подошли к моменту, когда мы начнет уже писать код! Дальше мы купим номер у Nexmo, настроим отправку SMS при создании новой заявки, и дадим возможность пользователям отвечать на заявку через SMS.

Покупка номера у Nexmo

Ранее мы упомянули что нам необходим телефонный номер Nexmo чтобы мы смогли получать на него SMS, и получать об этом уведомления на наш webhook. Чтобы облегчить всем жизнь, мы будем использовать один и тот же номер как для исходящих, так и для входящих SMS.

Вы можете купить и настроить телефонный номер через Nexmo Dashboard, но сегодня мы будем использовать консольную утилиту Nexmo CLI.

Чтобы купить номер, нам нужно сперва использовать команду number:search чтобы найти номер с поддержкой SMS и Голоса, а затем с помощью команды number:buy купить выбраный номер. В примере ниже мы покупаем US номер, но вы можете выбрать любой номер в одной из 45+ стран поддерживаемых Nexmo.

$ nexmo number:search US --sms --voice
$ nexmo number:buy --confirm

Запишите номер телефона который вы выбрали, так как он нам понадобится очень скоро.

Настройка Nexmo

Чтобы иметь возможность отправлять SMS через стандартную систему нотификаций в Laravel, нам необходимо установить пакет nexmo/client с помощью composer и настроить Key и Secret ключи и номер телефона от нашего аккаунта в Nexmo.

Установите nexmo/client пакет с помощью команды composer require nexmo/client. Затем отредактируйте файл config/services.php и добавьте в него эти строки:

'nexmo' => [
  'key' => env('NEXMO_KEY'),
  'secret' => env('NEXMO_SECRET'),
  'sms_from' => env('NEXMO_NUMBER'),
],

Затем добавьте строки ниже, в ваш .env файл:

NEXMO_KEY={YOUR_KEY}
NEXMO_SECRET={YOUR_SECRET}
NEXMO_NUMBER={YOUR_NUMBER}

На этом настройка Nexmo и системы нотификаций закончена.

Отправка нотификаций

Мы подошли к моменту, когда мы можем отправлять нотификации. Но у нас нет никаких нотификаций которые мы могли бы отправить! Создайте новую нотификацию с помощью команды php artisan make:notification TicketCommand и откройте созданный файл (app/Notifications/TicketCreated.php).

Первое что нам нужно изменить, находится на строчке 32, где функция via возвращает mail. Мы не хотим отправлять это через email, так что замените "mail" на "nexmo".

Наш новый класс знает как форматировать сообщение для email, но не знает как делать это для SMS. Чтобы решить эту проблему, добавьте новый метод:

public function toNexmo($notifiable)
{
  return (new NexmoMessage)
    ->content($this->entry->content);
}

В этом методе используются для объекта - NexmoMessage и $this->entry. Добавьте эти строки в наш файл:

use App\TicketEntry;
use Illuminate\Notifications\Messages\NexmoMessage;

Так же измените метод __construct() следующим образом:

protected $entry;

public function __construct(TicketEntry $entry)
{
  $this->entry = $entry;
}

Теперь у нас есть нотификация, которую мы можем отправить пользователю, но мы пока что не знаем кому конкретно нужно отправять данную нотификацию.

Заяка (ticket) может иметь несколько пользователей, а пользователь можно быть подписан на несколько заявок. Чтобы рассказать Laravel об этом, нам нужно добавить в класс Ticket новый метод:

public function subscribedUsers()
{
  return $this->belongsToMany(User::class, 'ticket_subscriptions');
}

После этого откройте ваш TicketController и добавьте следующие строки:

use App\Notifications\TicketCreated;
use Notification;

И наконец, нам нужно уже как-то отправить нотификацию пользователю. Чтобы сделать это, добавьте код ниже в конец метода [email protected], сразу перед строчкой с редиректом:

Notification::send($ticket->subscribedUsers()->get(), new TicketCreated($entry));

Эта строчка получает всех пользователей подписанных на данную заявку и отправляет им SMS. Сохраните все изменения, и попробуйте создать новую заявку (не забудьте указать "1" в поле "Recipient User ID". Через несколько секунд вы должны будете получить SMS с текстом заявки.

Подытожим что было сделано:

  • Создана новая нотификация с помощью команды php artisan make:notification
  • Изменен метод via, чтобы он возвращал nexmo.
  • Добавлен новый метод toNexmo() чтобы Laravel знал как составлять сообщение для данного сервиса.
  • Добавлен вызов Notification::send() для отправки нотификации

Весь код который был выше, вы можете найти на Github.

Получение SMS ответов

Итак, наши пользователи уже получают SMS с текстом заявок, что же дальше? Они должны открывать на компьютере наш сайт, чтобы ответить на зявку?

На данный момент у нас даже нету функционала, который позволил бы пользователю отвечать на заявку. Нам все равно придется потратить время чтобы добавить этот функционал, так давайте же сделаем его удобным для пользователей. Добавим возможность отвечать на заявку прямо из SMS.

Убедитесь что HTTP метод вашего webhook в настройках Nexmo выставлен на POST (по-умолчанию используется GET).

Когда Nexmo получает SMS на ваш номер, они отправляют HTTP запрос на webhook указанный в настройках. К сожалению наше приложение запущено локально, и Nexmo никак не сможет с ним связаться. Если вы еще не установили Ngrok - самое время это сделать. Прочитайте документацию и установите его.

Чтобы ваш локальный сервер был доступен через интернет, выполните команду ngrok http 8080. После запуска вы увидите много различно информации, нас же интересует только URL который ngrok выдал нам. Он находится в секции Forwarding и выглядит примерно так: http://abc123.ngrok.io.

Ngrok Session

После того как вы найдете свой URL, который вам выдал Ngrok, вам необходимо сказать Nexmo чтобы он использовал этого адрес в качестве Webhook. Выполните команду:

$ nexmo link:sms {NEXMO_NUMBER} http://{NGROK_URL}/ticket-entry

Заменив {NEXMO_NUMBER} и {NGROK_URL} на ваши данные.

Давайте быстренько проверим что все работает как надо. Откройте TicketEntryController и заменит метод store на код ниже:

public function store(Request $request)
{
  error_log(print_r($request->all(), true));
}

Это код будет выводить на экран полученный запрос (у вас до сих пор должна быть запущена команда artisan serve). Осталась еще одна маленькая правка, нам надо отключить CSRF проверку для нашего ticket-entry маршрута.

По-умолчанию все POST/PUT/DELETE маршруты защищены с помощью CSRF проверки. Чтобы отключить ее для конкретного маршрута, откройке файл app/Http/Middleware/VerifyCsrfToken.php и 'ticket-entry' в массив $except:

protected $except = [
  '/ticket-entry'
];

Если вы сейчас ответите на SMS, которую вы получили ранее, вы должны увидеть в консоле вывод запроса от Nexmo, который выглядит примерно так:

Nexmo webhook example

Если вы увидели похожий запрос - значит интеграция прошла успешно, и теперь каждый раз когда Nexmo будет получать SMS, они будут присылать вам уведомление об этом.

Заключительная вещь которую нам осталось сделать, это заменить print_r вызов (который мы добавили выше), реальным кодом, который будет добавлять ответ к заявке. Так как мы не можем связать SMS и заявку на которую пользователь ответил, нам остается только найти последнюю заявку в которой была активность, из тех на которые подписан данный пользователь, и предположить что пользователь ответил именно на эту заявку. (конечно ужасный способ, но как-то так... прим. пер.)

Давайте вынесем эту логику в отдельный метод в модели User. Откройте файл app/User.php и добавьте этот метод:

public function latestTicketWithActivity() {
  // Get all tickets this user is watching
  $watchedTickets = TicketSubscription::select('ticket_id')->where('user_id', $this->id)->get()->pluck('ticket_id');

  // Grab the latest ticket with activity that's in this list
  $latestTicketEntry = TicketEntry::select("ticket_id")->whereIn('ticket_id', $watchedTickets)->orderBy('created_at', 'desc')->limit(1)->first();

  // Fetch the actual ticket
  return $latestTicketEntry->ticket()->first();
}

Этот метод возвращает объект Ticket, в котором содержиться самая новая запись TicketEntry. Теперь у нас есть все чтобы добавить ответ из SMS к нашей заявку.

Опять откройте файл TicketEntryController и замените вызов print_r кодом ниже. Этот код находит пользователя в базе данных по его номеру телефона, достает Ticket и добавляет к нему ответ.

// Make sure it's in the correct format
$data = $this->validate($request, [
  'msisdn' => 'required',
  'text' => 'required'
]);

// Find the user based on their phone number
$user = User::where('phone_number', $data['msisdn'])->firstOrFail();

// And then find their latest ticket
$ticket = $user->latestTicketWithActivity();

// Create a new entry with the incoming SMS content
$entry = new TicketEntry([
  'content' => $data['text'],
  'channel' => 'sms',
]);

// Attach this entry to the user and ticket, then save
$entry->user()->associate($user);
$entry->ticket()->associate($ticket);
$entry->save();

return response('', 204);

Так же добавьте эти 2 импорта вверху:

use App\User;
use App\TicketEntry;

Сохраните все изменения и снова отправьте SMS. Если вы сейчас обновите страницу с заявками, вы должны увидеть что у вашей заявки появился ответ, который вы отправили в SMS.

Получать SMS с Nexmo даже проще чем посылать их! Нам понадобилось всего 5 простых шагов:

  • Настроить webhook для нашего номера
  • Запустить ngrok, чтобы он позволил Nexmo обращаться к нашему локальному серверу
  • Отключить CSRF защиту для webhook маршрута
  • Получить последнюю заявку
  • Сохранить полученное сообщение как ответ на зявку

Весь код выше вы сможете найти на Github.

Заключение

В этой статье мы научились отправлять и получать SMS нотификации используя Nexmo и стандартную систему нотификаций Laravel, а так же сохранять полученные SMS в базу данных.

В следующей части, мы добавим поддержку голосовых звонков. Ведь звонящий телефон сложнее игнорировать чем SMS. Для заявок, которые критичны к времени реагирование - это может быть большой проблемой.

Опубликовано:

Категории: Статьи