LINEプラットフォームを利用したヘアサロン予約ミニアプリを制作する機会がありましたので、手順をまとめておきます。審査については別途記事にまとめたいと思います。
LINEミニアプリは、(1)LINE APIと(2)(LINEの外にホスティングされた)webアプリ、の2つの構成要素で成り立っています。ここでは(2)のヘアサロン予約のwebアプリはほぼ構築ずみとし、予約確定時にLINEメッセージ送信する機能を追加する状況を想定しています。
LINEミニアプリ チャンネル登録
- LINE Developersに登録、ログイン
- プロバイダー登録(サービスを提供する主体)
- アプリ名登録(ミニアプリとして登録)
以上を行うと、アプリのチャンネルが生成されます。APIの利用にはLIFF-IDが必要になります。分かりづらいですが、LIFFのタブにあるLIFF URLに記載があるので、これを環境変数に設定しておきます。ちなみにLIFFとはLINE Front-end Frameworkの略称です。
LIFF URL: https://miniapp.line.me/{この部分がLIFF-ID}
エンドポイントURLには、作成ずみwebアプリのURLを入力します。これで外部ブラウザで作成ずみwebアプリとLINEを連携して使用できるようになります。また上記のLIFF URLを開くとQRコードが生成されますが、これをLINEがインストールされたスマホで開くとLINEアプリ上で作成したwebアプリが動きます。
webアプリ側のLIFF API利用設定
今回のwebアプリはNext.jsで作成していますので、それを前提に話を進めます。以下でLIFFをインストールします。
npm install @line/liff
基本的には以下の形でliffを初期化して使用できます。
import { Liff } from '@line/liff';
const liffId={process.env.NEXT_PUBLIC_LIFF_ID || ''}
liff.init({ liffId })
今回のアプリでは、liffをアプリ全体で使用できるようカスタムフックを使って、layout.tsxに配置しています。
/components/LiffProvider.tsx
'use client';
import React, {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import { Liff } from '@line/liff';
const LiffContext = createContext<{
liff: Liff | null;
liffError: string | null;
}>({ liff: null, liffError: null });
export const useLiff = () => useContext(LiffContext);
export const LiffProvider: FC<PropsWithChildren<{ liffId: string }>> = ({
children,
liffId,
}) => {
const [liff, setLiff] = useState<Liff | null>(null);
const [liffError, setLiffError] = useState<string | null>(null);
const initLiff = useCallback(async () => {
try {
const liffModule = await import('@line/liff');
const liff = liffModule.default;
console.log('LIFF init...');
await liff.init({ liffId });
console.log('LIFF init succeeded.');
setLiff(liff);
} catch (error) {
console.log('LIFF init failed.');
setLiffError((error as Error).toString());
}
}, [liffId]);
// init Liff
useEffect(() => {
console.log('LIFF init start...');
initLiff();
}, [initLiff]);
return (
<LiffContext.Provider
value={{
liff,
liffError,
}}
>
{children}
</LiffContext.Provider>
);
};
layout.tsx
import type { Metadata } from "next";
import { LiffProvider } from "@/components/LiffProvider"
import "./globals.css";
export const metadata: Metadata = {
title: "eQOL予約",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja">
<body className="w-screen h-screen">
<LiffProvider liffId={process.env.NEXT_PUBLIC_LIFF_ID || ''}>
{children}
</LiffProvider>
</body>
</html>
);
}
liffからgetProfileメソッドユーザー情報を取得することができます。取得可能なユーザー情報は、「ユーザーの表示名」「ユーザーID」「ユーザー画像URL」ですが、メッセージ送信に必要なのは「ユーザーID」です。
useEffect(() => {
liff.getProfile()
.then((profile) => {
setUserId(profile.userId)
})
.catch((err) => {
console.log("error", err);
});
}, [liff]);
サービスメッセージ
サービスメッセージを送信する手順は、1.メッセージのテンプレートを作成、2.サービス通知トークンを発行してメッセージ送信、3. 2のトークンを利用して後続のメッセージ送信、となります(公式ドキュメント)。
1. メッセージテンプレートの作成
サービスメッセージはテンプレートに沿って作成する必要があり、そのテンプレートは複数あって目的に会うものを選択することになります。不要な項目を削除する以外の編集はできないようです。以下はBooking confirmed(detailed)テンプレートの内容になります。

{
"sum": "5,000 円",
"date": "2033/8/22 0:00",
"name": "ブラウン",
"count": "5人",
"number": "1357",
"address": "東京都新宿区四谷1-6-1",
"btn1_url": "https://line.me",
"btn2_url": "https://line.me",
"btn3_url": "https://line.me",
"btn4_url": "https://line.me",
"condition": "事前に決済を済ませてください",
"shop_name": "ブラウン新宿店",
"charge_name": "株式会社ブラウン",
"user_number": "1234",
"reservation_contents": "通常エアコン1台"
}
変更できる部分は予約番号から下で、タイトル(キー)は変更できませんが、不要な項目は削除できます。
2.サービス通知トークンの取得
サービス通知トークンの取得の流れです。ステートレスチャンネルアクセストークンとLIFFのアクセストークンを使ってサービス通知トークンを取得します。

LIFFアクセストークンは以下にて取得できます。
const accessToken = liff.getAccessToken()
ステートレスチャンネルアクセストークンは以下の形で取得できます。
const issueToken = async () => {
try {
const response = await fetch("https://api.line.me/oauth2/v3/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.NEXT_PUBLIC_LINE_CHANNEL_ID!,
client_secret: process.env.NEXT_PUBLIC_LINE_CHANNEL_SECRET!,
}),
});
const jsonData = await response.json();
if (jsonData){
setAccessToken(jsonData.access_token)
}
} catch(error) {
console.error('Error fetching data:', error);
}
}
LIFFアクセストークンとステートレスチャンネルアクセストークンから以下でサービス通知トークンを取得できます。
const issueNotifierAccessToken = async (liffToken:string, accessToken:string) {
try {
const response = await fetch("https://api.line.me/message/v3/notifier/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({
liffAccessToken: liffToken
})
});
const jsonData = await response.json();
if (jsonData){
return jsonData
} else {
{access_token:""}
}
} catch(error) {
console.error('Error fetching data:', error);
return {access_token:""}
}
}
これでようやくメッセージを送信するのに必要な準備が整いました。以下は送信例です。
const sendMessage = (accessToken:string, notificationToken:string) => {
//作成したテンプレートに従ってメッセージ内容を設定
const message:Message = {
templateName:{template_name + ja},
params:{
date:{予約日時},
address:{店舗住所},
shop_name:{店舗名},
charge_name:{スタッフ},
reservation_contents:"{サービス内容},
btn1_url:{エンドポイントURL}
},
notificationToken:notificationToken
}
//メッセージ送信
try {
const response = await fetch("https://api.line.me/message/v3/notifier/send?
target=service", {
method: "POST",
headers: {
"Content-Type": "application/json",
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify(message)
});
const jsonData = await response.json();
if (jsonData){
return jsonData
} else {
return {notificationToken:""}
}
} catch(error) {
console.error('Error fetching data:', error);
return {notificationToke:""}
}
}
以上駆け足でしたが、作成ずみwebアプリ(Nextjs)をLINEアプリとして改変して、LINEメッセージを送信する方法を紹介しました。次回の記事では、この受付完了メッセージの後、後続で予約日の前日に確認メッセージを送る方法を紹介します。


