【Laravel】店先でLINE友達登録し、会員登録と連携する

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、この間新しいメガネに買い換えるためにCMでもよく見かけるお店に行くことになったのですが、その際にあるシステムが目に止まりました。

それは・・・・・・

LINEでお友達になると、メッセージで商品到着のお知らせだけでなく、後ほど購入済みの内容まで知ることができる

というシステムでした。

例えば、メッセージ内に次のようなボタンが表示されて、タップするとその内容が表示されることになります。

素直に「へぇ、すごい便利❗」と思ったのですが、その反面「これ、どうやって実装してるんだろう??」とシステム・エンジニアとしての興味が湧いてきました。

そして、せっかくなら大好きなLaravelで作ってみようということで今回の記事をお届けすることになりました。

ぜひ何かの勉強になりましたら嬉しいです。


「仕事のためなので
メガネはフル装備で
注文しました❗」

開発環境: Laravel 8.x

前提として

前提として、Laravelにログイン機能がインストールされていることが前提です。(今回はテストユーザーは必要ありません👍)

📝 参考ページ

また、今回はLINEAPIを使いますので、LINE Developersに登録しておいてください。

📝 参考ページ: LINE Developersにアカウントを作る

※ 少し昔の記事ですので、現在とは違っている可能性があります。

実装する手順

では、作業に入る前にどのように実装するかをご紹介します。
手順は実際私が体験した流れを元にしています。

  1. 店頭でお店のアカウントとLINEでお友達になる
  2. 会員登録のリンクが送信されてくる(ウェブフック)
  3. リンクをクリックすると、まず「LINEログイン」へアクセス(OAuth)
  4. 自分の情報を使うことを許可
  5. 許可された情報を使って会員ページへリダイレクト
  6. 名前やパスワード、住所など残りの情報を入力して完了

さすが有名な企業さん。
全てがスムーズに済むように設計されていますね 👍

ただし、6番目の項目は今回の内容からは少し外れるので割愛して開発することにします。(5番の時点で会員登録が完了し、ログインさせます)

準備する

では、まずプログラムより先に準備しておくことがありますので、そこから見ていきましょう。

Messaging APIが使えるようにする

LINE Developersにログインし、チャット用に「チャネル」を作ってください。

📝 参考ページ: Messaging APIを有効にする

※ 時間が経ち少し表示が変わっていますが、基本的には同じ内容になっています。

アカウント作成が完了したら、「チャネルシークレット」「チャネルアクセストークン」を取得して.envにそれぞれ以下のように書き込んでおきます。

LINE_CHANNEL_SECRET=*************************
LINE_ACCESS_TOKEN=****************************************

なお、取得する場所はタブが違いますので注意してください。

ウェブフックを有効にする

お友達登録」と「お友達登録の解除」された場合にアクセスさせるウェブフックを有効にします。

Messaging API 設定」タブの中で以下のようにして設定してください。

なお、ウェブフックのURLはCSRFトークンチェックを解除しておく必要があります。

app/Http/Middleware/VerifyCsrfToken.php

// 省略

class VerifyCsrfToken extends Middleware
{
    // 省略

    protected $except = [
        'line/registration/webhook' // 👈 こちらを追加しました
    ];
}

また、Webhookをオンにするのも忘れないようにしてください。

LINEログインを有効にする

LINEログインは「ソーシャルログイン」と呼ばれるもので、今回は LINEに登録されたメールアドレスを取得するため だけ使います。

このソーシャルログインを有効にするために、以下のページを参考にして、

  • アクセス情報の取得
  • コールバックURLの設定
  • メールアドレス取得権限

を済ませておいてください。

📝 参考ページ: LINEからソーシャルログインに必要な情報を取得する

パッケージをインストールする

これから使うパッケージのインストールをしておきます。
以下のコマンドで実行してください。

LINE API パッケージ

composer require linecorp/line-bot-sdk

画像を操作するパッケージ

composer require intervention/image

ソーシャルログイン用のパッケージ

composer require laravel/socialite
composer require socialiteproviders/line

なお、socialiteproviders/lineは、Laravelにセットする必要がありますので、以下のようにしてください。

app/Providers/EventServiceProvider.php

// 省略

use SocialiteProviders\Line\LineExtendSocialite; // 👈 ここを追加しました
use SocialiteProviders\Manager\SocialiteWasCalled; // 👈 ここを追加しました

class EventServiceProvider extends ServiceProvider
{
    // 省略

    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        SocialiteWasCalled::class => [ // 👈 ここを追加しました
            LineExtendSocialite::class
        ]
    ];

では、お待たせしました。
次の項目から、実際にLaravel開発を楽しんでやっていきましょう❗

モデル&マイグレーションをつくる

まず、今回必要なline_usersテーブルをつくっていきます。
以下のコマンドを実行してください。

php artisan make:model LineUser -m

すると、モデルとマイグレーションのファイルが作成されるので、マイグレーションの中身を以下のように変更してください。

database/migrations/****_**_**_******_create_line_users_table.php

// 省略

Schema::create('line_users', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id')->nullable()->comment('ユーザーID');
    $table->string('line_id')->comment('LINEのID');
    $table->string('mode')->comment('チャネルの状態'); // `standby` は送信すべきでない
    $table->string('display_name')->comment('LINEの名前');
    $table->timestamps();

    $table->foreign('user_id')->references('id')->on('users');
});

変更が完了したら、以下のコマンドでテーブルを作成してください。

php artisan migrate

すると実際のテーブルはこうなります。

ウェブフック部分をつくる

では、続いて「お友達になった」「お友達を解除した」状態になったとき実行するウェブフックをつくっていきます。

ウェブフック返信用の画像を用意する

今回、LINEでお友達登録が完了したら「タップできる画像メッセージ(イメージマップメッセージ)」を返信します。

ただ、イメージマップメッセージを送信するためには、以下のURLにも書かれている通り、同じ画像で5つのサイズを用意する必要があります。

📝 参考URL: イメージマップメッセージ

さらに、LINEの仕様でこのURLには「.png」など拡張子を含めてはいけないということになっているので、今回は一番大きな画像(1040 x 1040 ピクセル)を用意し、各サイズにリサイズできるようにします。

なお、画像は以下のものを使います(サンプルなので小さくしてます)

では、送信したい画像を以下のように、
/storage/app/images/line_user_registration.png
として保存します。

コントローラーをつくる

次に、コントローラーをつくります。
以下のコマンドを実行してください。

php artisan make:controller LineRegistrationController

するとファイルが作成されますので、中身を以下のように変更してください。

app/Http/Controllers/LineRegistrationController.php

<?php

namespace App\Http\Controllers;

use App\Models\LineUser;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use LINE\LINEBot;
use LINE\LINEBot\Event\FollowEvent;
use LINE\LINEBot\Event\UnfollowEvent;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot\ImagemapActionBuilder\AreaBuilder;
use LINE\LINEBot\ImagemapActionBuilder\ImagemapUriActionBuilder;
use LINE\LINEBot\MessageBuilder\Imagemap\BaseSizeBuilder;
use LINE\LINEBot\MessageBuilder\ImagemapMessageBuilder;
use LINE\LINEBot\MessageBuilder\TextMessageBuilder;

class LineRegistrationController extends Controller
{
    private $channel_secret, $access_token;

    public function __construct() {

        $this->channel_secret = env('LINE_CHANNEL_SECRET');
        $this->access_token = env('LINE_ACCESS_TOKEN');

    }

    public function image($size) {

        $path = storage_path('app/images/line_user_registration.png');
        $image = \Image::make($path);

        if($size < 1040) {

            $image->resize($size, $size);

        }

        return $image->response();

    }

    public function callback(Request $request) {

        $socialite_user = Socialite::driver('line')->stateless()->user();
        $socialite_id = $socialite_user->getId();
        $socialite_email = $socialite_user->getEmail();
        $socialite_name = $socialite_user->getName();
        $line_user = LineUser::where('line_id', $socialite_id)->first();

        if(!is_null($line_user) && !is_null($socialite_email)) {

            \DB::beginTransaction();

            try {

                $user = User::firstOrNew(['email' => $socialite_email]);
                $user->email = $socialite_email;
                $user->name = $socialite_name;
                $user->password = Hash::make(Str::random()); // パスワードはランダム
                $user->save();

                $line_user->user_id = $user->id;
                $line_user->save();

                $line_id = $line_user->line_id;
                $client = new CurlHTTPClient($this->access_token);
                $bot = new LINEBot($client, ['channelSecret' => $this->channel_secret]);
                $text_message = new TextMessageBuilder('会員登録が完了しました!');
                $bot->pushMessage($line_id, $text_message);

                auth()->login($user); // 自動ログイン
                \DB::commit();

                return '会員登録が完了しました!';

            } catch (\Exception $e) {

                // ここでエラー処理
                \DB::rollBack();

            }

        }

        return '必要な情報が取得できていません。';

    }

    public function webhook(Request $request) {

        $request_body = $request->getContent();
        $hash = hash_hmac('sha256', $request_body, $this->channel_secret, true);
        $signature = base64_encode($hash);

        if($signature === $request->header('X-Line-Signature')) { // ここでLINEからの送信を検証してます

            $client = new CurlHTTPClient($this->access_token);
            $bot = new LINEBot($client, ['channelSecret' => $this->channel_secret]);

            try {

                $events = $bot->parseEventRequest($request_body, $signature);

                foreach($events as $event) {

                    $line_id = $event->getEventSourceId();
                    $reply_token = $event->getReplyToken(); // 返信用トークン

                    if($event instanceof FollowEvent) { // お友達登録されたとき

                        // DBへ取得情報を保存
                        $mode = $event->getMode();
                        $profile = $bot->getProfile($line_id)->getJSONDecodedBody();
                        $display_name = $profile['displayName'];

                        $line_user = LineUser::firstOrNew(['line_id' => $line_id]);
                        $line_user->mode = $mode;
                        $line_user->display_name = $display_name;
                        $line_user->save();

                        // 自動返信(登録リンク送信)
                        $width = 1040;
                        $height = 1040;
                        $alt_text = '会員登録できます!'; // 代替テキスト
                        $base_url = route('line.registration.image', ''); // 画像URL
                        $base_size = new BaseSizeBuilder($height, $width); // 基本画像のサイズ

                        $x = 0;
                        $y = 0;
                        $area = new AreaBuilder($x, $y, $width, $height);
                        $link_url = Socialite::driver('line') // LINEログインのURL
                            ->redirect()
                            ->getTargetUrl();
                        $image_map_actions = [ new ImagemapUriActionBuilder($link_url, $area)];

                        $image_map_message = new ImagemapMessageBuilder(
                            $base_url,
                            $alt_text,
                            $base_size,
                            $image_map_actions
                        );
                        $bot->replyMessage($reply_token, $image_map_message);

                    } else if($event instanceof UnfollowEvent) { // お友達登録が解除されたとき

                        LineUser::where('line_id', $line_id)->delete();

                    }

                }

            } catch (\Exception $e) {

                // ここでエラー処理

            }

        }

    }
}

コードが長いのですが、やっていることはシンプルに次のとおりです。

image()

5つのサイズの画像を用意する

callback()

LINE ログインで許可した場合にリダイレクトされたときに実行される部分です。この中で会員登録と完了メッセージの送信をしています。

webhook() 

お友達登録&解除したときにLINEから「ウェブフック」としてアクセスしてくる部分です。この中では、LINEユーザーの登録と自動返信をしています。

ルートをつくる

そして、先ほどのコントローラーを使うためのルートを追加してください。

routes/web.php

// 省略

use App\Http\Controllers\LineRegistrationController;

// 省略

Route::prefix('line')->group(function(){

    Route::get('registration/image/{size}', [LineRegistrationController::class, 'image'])
        ->where('size', '240|300|460|700|1040')
        ->name('line.registration.image');
    Route::get('registration/callback', [LineRegistrationController::class, 'callback']);
    Route::post('registration/webhook', [LineRegistrationController::class, 'webhook']);

});

ちなみに

今回は画像をタップすると特定のリンクへ飛ぶだけでしたが、なんとLINEの「イメージマップメッセージ」は、

この部分は、******.com に飛ぶ

というようにタップする場所によって様々なアクションを使い分けることができます。

例えば、冒頭で紹介した画像のようにタップする場所に応じてリンク先を変更したりできるわけです。

なお、実際に私が体験したお店では、タップする場所によって「会員証」や「購入履歴」とメッセージ送信することになり、それをキーワードにして会員情報が自動的に返信されるようにしていました。

さすがですね 👍

テストしてみる

では、実際にテストしてみましょう❗

まず、LINE Developersで作ったMessaging APIチャネルとお友達になります。

※ お友達になるには、「Messaging API 設定」タブのQRコードを使うと楽です👍

すると・・・・・・

ピンポーン!という音とともに画像が送られてきました。
うまくいっています😊

では、次に画像をタップして会員登録できるかみてみましょう。

どうなるでしょうか・・・・・??

はい❗LINEログイン画面に移動しました。(英語表記なのは私の設定のせいです。まだまだ勉強中なので…)

では、「Allow(許可する)」ボタンをクリックしてみます。

すると・・・・・・

アプリ内のブラウザで会員登録の完了メッセージが表示されました。

そして、

LINEのプッシュ・メッセージの方も送信されてきました。
成功です😊✨

あとは、同様にLINE IDを使えばいつでもメッセージ送信することができますので、「商品が完成しました」メールや事前に許可が必要ですが、割引セールのお知らせなどもできるようになります。

企業様へのご提案

今回の機能を使うと、以下のようなことメリットがあると考えています。

  • LINEのお友達登録はQRコードを読み取るだけですぐ完了できるので、会員登録に誘導しやすい(後ほど名前などを登録するにしても、入り口がシンプルなのでより登録しようと考えやすい)
  • お客さんからすると、いちいちログイン情報を覚えていなくてもタップするだけで過去の情報にアクセスできるので便利
  • LINEのお友達を解消されても、会員データ自体は残る。(ログインもできる)
  • LINEメッセージには画像だけでなく、動画や位置情報などさまざまなものがあるため、希望の機能をつくりやすい
  • なによりLINEの普及率が大きい(2020年9月末時点で、国内の月間利用者数は、8,600万人: 引用資料

ぜひ今回の機能でシステム開発をお考えの場合はお問い合わせよりご連絡ください。m(_ _)m

おわりに

ということで、今回は実体験を元にして「LINEを使った会員登録システム」をつくってみました。

正直なところ想定していたよりボリュームが大きくなってしまいましたが、過去に書いた記事のおかげでほぼどこにもハマることなく作業を進めることができました。(ブログを書くということは、過去の自分に助けてもらえるということなんですね👍)

なお、今回メガネを買い替えたのは、鼻の部分を固定する金具がボッキリ折れてしまったからなんですが、買い替えてみると、最近のメガネってホントに軽いんだなと実感します。

気分がいいので、これを機に毛先を遊ばせてみようかな❓❓

ということで、ぜひ皆さんもLINEを使って会員登録システムをつくってみてくださいね。

ではでは〜❗

「1週間以上かかるところ、
3日で仕上げてくれました」

開発のご依頼お待ちしております 😊✨ お問い合わせ
また、こちらもお待ちしております。
  • 実案件の開発サポート: 詳細
  • ツイッターのフォロー: 詳細
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly