LIFFを使ってサービスを作ってみた

LIFFの使い所とかLIFFでもメッセージを送る方法とか

おはようございます、

最近、プライベートで簡易的な宿泊予約サービスを作る機会がありました。
そのサービスでLIFFを実装することになったので簡単な使い方と触ったTIPSを残しておきます

どんなの?

/img/2023-06-29/01.png

予約申し込み機能

/img/2023-06-29/02.png

管理機能

サービスには大きく分けて予約申し込み機能と管理機能の2つあり、そのうち予約申し込み機能でLIFFを導入しました。

利用者は高齢の方が多くITリテラシーがあまり高くはないとのこと、ですがLINEはインストールしてる方が多いらしいのでLINE/LIFFにしました。特にLIFFはLINEミニアプリと違いLINE運営への申請が不要で手軽に始めることができました。

具体的にLINEおよびLIFFは以下の目的で利用しました

  • 予約申し込み完了時に、LINE公式アカウントから申し込み内容の控えをメッセージを送る
  • 確定時に、LINE公式アカウントから確定内容の控えをメッセージを送る
  • 予約申し込み時に、利用者のLINEのユーザーIDをもとにして、前回予約したことがある場合は一部項目を自動入力する

どうやった?

LINE Developers上の構成は?

LINEで何かしらのカスタマイズをしようとすると、LINE Developersでチャネルを作成する必要がありました。
チャネルにはLINEログイン/Messaging API/ブロックチェーンサービス/LINEミニアプリの4つがあり、今回のようなLIFFアプリを作成する際にはLINEログインを選択する必要があります。

ですが、実はLINEログインチャネル(=LIFF)にはLINEミニアプリでは可能なメッセージ送信機能が存在せず、LIFF単体ではユーザーにメッセージを送れません。
この問題は、別途MessagingAPIチャネルおよび付随する公式アカウントを作成しそのチャネルからメッセージを送るようにすることで解決しました
同じユーザーでもチャネルによってユーザーIDが異なっていないか心配だったのですが同じだったためこの方法が使えました。

また、この構成だと公式アカウントと友達になっていない状態で予約申し込み/確定した場合はメッセージが送れなくなってしまうのですが、LIFFアプリへの動線を公式アカウントの中だけに仕込むことで解決させることができました。

/img/2023-06-29/03.jpeg

公式アカウントのリッチメニュに動線を用意

どんな実装?

利用者から見た申し込みまでの流れは以下の通りです

  1. (初回のみ)公式アカウントを友達登録
  2. 公式アカウントのリッチメニューからLIFFアプリ起動
  3. 過去に予約していれば氏名など自動入力
  4. 予約申し込みを行う
  5. 申し込み内容の控えメッセージを送る
  6. LIFFアプリを閉じる

2と5と6で実際に利用したAPIについて具体的に書いていきます

ちなみに、LIFFはnpmパッケージで公開されており、普通のパッケージと同じくtsやjsの中でimportして利用することができます。

2.公式アカウントのリッチメニューからLIFFアプリ起動

LIFF側でIDトークンを取得し、それをバックエンドに送ります

liff.getIDToken()
LIFF SDKが取得した「現在のユーザーの生のIDトークン」を取得します。IDトークンは、ユーザー情報を含むJSONウェブトークン(JWT)です。

https://developers.line.biz/ja/reference/liff/#get-id-token

このIDトークンにはsubとしてユーザーIDが含まれており、それをもとにしてバックエンドは利用者の過去の予約を検索することができます
似たようなAPIにliff.getDecodedIDToken()があり、こちらだとLIFFで簡単にユーザーIDを取得しそれをバックエンドに送れるのですが、なりすましのリスクがあるので使ってはダメです。IDTokenの場合だとバックエンドでデコードした後に署名の検証を行うことができます

5.申し込み内容の控えメッセージを送る

ここでも上記と同様のAPIを利用し、LIFF側でIDトークンを取得しそれをバックエンドに送っています。
バックエンド側では、MessagingAPIチャネルから得たトークンを利用してメッセージを送信するAPIを叩きます

MessagingAPIにはいろいろな種類のメッセージ送信が用意されていますが、今回は単一のユーザーに任意のタイミングで送りたいのでPOST https://api.line.me/v2/bot/message/pushを利用します

プッシュメッセージを送る
POST https://api.line.me/v2/bot/message/push
ユーザー、グループトーク、または複数人トークに、任意のタイミングでメッセージを送信するAPIです。

https://developers.line.biz/ja/reference/messaging-api/#send-push-message

僕の場合はバックエンドをGASで組みました。コードはこんな感じです

/**
 * メッセージ送信
 */
function sendMessage(payload) {
  const options = {
    'method': 'post',
    'payload': JSON.stringify(payload),
    headers: {
      "content-type": "application/json",
      "authorization": "Bearer {MessagingAPI token}"
    }
  };
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', options);
}

リクエストボディには何が入る?

上記APIに渡すリクエストボディ(上記例で言うpayload)には送信したいメッセージとユーザーのIDを含めます。シンプルに文字を送るだけなら以下の通りで十分です。

{
    "to": "{user ID}",
    "messages":[
        {
            "type":"text",
            "text":"{your message}"
        }
    ]
}

ですが、今回はせっかくなので柔軟なメッセージを送ることができるFlexMessageを利用してみました。
FlexMessageは上記の例でいうtypeflexにすることで利用可能です。

また、その中身のフォーマットはLINEが公式でFLEX MESSAGE SIMULATORを用意してくれているのでこれを使って組み立てると楽です

その他いろいろなことができるので以下を参考にしてください

https://developers.line.biz/flex-simulator/

僕の場合はGASでこんな感じで作りました🚀

/img/2023-06-29/04.jpeg
{
        "type": "flex",
        "altText": "✅予約が確定しました",
        "contents": {
          "type": "bubble",
          "body": {
            "type": "box",
            "layout": "vertical",
            "contents": [
              {
                "type": "text",
                "text": "✅予約が確定しました",
                "size": "xl",
                "weight": "bold",
                "wrap": true
              },
              {
                "type": "text",
                "text": "以下の内容で予約が確定しました。",
                "wrap": true
              },
              {
                "type": "separator",
                "margin": "lg"
              },
              {
                "type": "text",
                "text": "ID:" + row[COLUMN.ID],
                "wrap": true,
                "margin": "lg"
              },
              {
                "type": "text",
                "text": "いつから:" + formatDate(row[COLUMN.STARTED_AT]),
                "wrap": true
              },
              {
                "type": "text",
                // TODO: 日付も計算したい
                "text": "いつまで:" + row[COLUMN.DURATION] + "泊",
                "wrap": true
              },
              {
                "type": "text",
                "text": "大人(男性):" + row[COLUMN.COUNT_OF_ADLUTS_MALE] + "人",
                "wrap": true
              },
              {
                "type": "text",
                "text": "大人(女性):" + row[COLUMN.COUNT_OF_ADLUTS_FEMALE] + "人",
                "wrap": true
              },
              {
                "type": "text",
                "text": "子供(男性):" + row[COLUMN.COUNT_OF_CHILDS_MALE] + "人",
                "wrap": true
              },
              {
                "type": "text",
                "text": "子供(女性):" + row[COLUMN.COUNT_OF_CHILDS_FEMALE] + "人",
                "wrap": true
              },
              {
                "type": "text",
                "text": "お名前:" + row[COLUMN.NAME],
                "wrap": true
              },
              {
                "type": "text",
                "text": "電話番号:" + row[COLUMN.PHONE_NUMBER],
                "wrap": true
              },
              {
                "type": "text",
                "text": "食事・宿泊人数\n" + formatMeals(JSON.parse(row[COLUMN.MEALS]), row[COLUMN.STARTED_AT]),
                "wrap": true
              },
              {
                "type": "separator",
                "margin": "lg"
              },
              {
                "type": "text",
                "text": "キャンセルや変更は電話でお問合せください",
                "wrap": true,
                "margin": "lg"
              }
            ]
          }
        }
      }

6.LIFFアプリを閉じる

ユーザーの操作なしにLIFFアプリを閉じることができます。予約申し込みが完了したタイミングでアプリの操作が不要なため、自動で閉じさせています

liff.closeWindow()
LIFFアプリを閉じます。

https://developers.line.biz/ja/reference/liff/#close-window

まとめ

  • LIFFはLINE上で完結するのでとても使いやすい
    • 宿の予約とか飛行機のチケットとか、友達とやりとりしながら入力するタイプのフォームはLIFFだとめちゃくちゃ快適だろうなと思います
    • 今回のように内容控えをメッセージで送ると、後から内容を見直すのが簡単(webサイトに都度ログインしなくて良い)
      • ログインしなくて良いのがめちゃくちゃ強い
  • LIFFでメッセージを送るためには別途Messaging APIチャネルと公式アカウントを用意する必要がある
    • 公式的にはあまり推奨されていないかも?
      • けどチャネルごとにユーザーIDが分離されていないってことは許容範囲なのかなとおもってます
  • FlexMessageの組み立てはCSSをJSONで表記するような形で面白い
    • この機能実装した人めちゃくちゃ大変だったんやろうなと感じました
    • いっそのこと今のhtmlのcssは廃止してこのフォーマットのJSONでもいいんじゃね?と少し思いました

誰かの役に立てば幸いです


See also