【LIFF】アプリ内で会員登録したユーザーと未会員ユーザーを識別する方法を考えてみた
リテールアプリ共創部のるおんです。
LIFFアプリの開発において、アプリ内で会員登録したユーザーと未会員ユーザーを識別する必要性に直面しました。
これまでは会員登録したユーザーのLINE UIDを取得し、会員向けにメッセージ配信を行なったり会員データを用いて分析をしたりしていました。
しかし、これだと会員登録をしてくれなかったユーザーに対してメッセージ配信などのアクションを実行することができませんでした。今回はこの課題に対する解決策を考案し実装してみましたので、その方法をご紹介します。
やりたいこと
これまでの実装では、会員登録をしたユーザーのLINE UIDのみをデータベースに保存していました。しかし、これだと会員登録をしなかったユーザーの情報を取得できていません。そのため、以下のような要求に応えることができませんでした。
- 公式アカウントを友達追加しLIFFアプリにアクセスしたが、会員登録まで至らなかったユーザーに対しても何らかのアクションを起こしたい。
- 会員登録での離脱率を知りたい
イメージとしては以下のように、黄色の矢印で表現した未会員層を識別できるようにすることです。これまでは、青色の層のみをDBの会員テーブルに保存していたので、未会員ユーザーの情報を取得できていませんでした。
これらの課題を解決するために、以下のアプローチを採用しました。
会員テーブルとは別に、一度でもアクセスしたユーザー全員を保存する専用テーブル(Guestテーブル)を作成し、そのテーブルにLINE UIDや名前の情報を格納するのに加えて、会員か未会員かのステータスを持たせるようにしました。
イメージとしては、以下の図のようなものです。
このようにすることで、GuestテーブルのisMember
ステータスがfalse
のユーザー群のLINE UIDを使用することで、未会員ユーザーのみに特定アクション(メッセージ一斉配信など)をすることができます。
実際にやってみた
ではこの構成を実際に実装してみました。フロントエンドにReact、バックエンドにAWS LambdaをNode.jsを使って実装します。データベースはAmazon DynamoDBを用いています。
以下は簡単な手順です。
- 専用テーブル作成
すべてのLIFFアプリアクセスユーザーの情報を保存するテーブル(Guestテーブル)を新設します。 - 初回アクセス時の情報送信(フロントエンド)
アプリの初期表示画面にて、ユーザー情報を取得するためのアクセストークンをバックエンドに送信します。 - ユーザー情報の保存(バックエンド)
受け取ったアクセストークンからユーザー情報を取得し、Guestテーブルに保存する。
会員登録をする際にisMember
ステータスを更新。
インフラストラクチャはAWS CDKを用いてサクッと作ってみました。
CDK
linUserIdをパーティションキーとするDynamoDBのGuestテーブルと、保存処理をするためのLambda、エンドポイントとしてのAPI Gatewayを作成しています。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as aws_dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { RemovalPolicy } from 'aws-cdk-lib';
export class GuestLiffTestStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
/**
* GuestUserを登録するLambda
*/
const registerGuestUserFn = new nodejs.NodejsFunction(this, "registerGuestUserFn", {
entry: "server/handler/registerGuestUserHandler.ts",
runtime: lambda.Runtime.NODEJS_20_X,
functionName: "registerGuestUserFn",
description: "アクセスしたユーザーをすべて保存するLambda関数",
architecture: lambda.Architecture.ARM_64,
});
/**
* ゲスト用のAPI Gateway
*/
const api = new apigateway.LambdaRestApi(this, "registerGuestUserApi", {
handler: registerGuestUserFn,
proxy: false,
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: apigateway.Cors.DEFAULT_HEADERS,
statusCode: 200,
},
});
const registerGuestUserIntegration = new apigateway.LambdaIntegration(registerGuestUserFn);
api.root.addMethod("POST", registerGuestUserIntegration)
/**
* ゲスト用のDynamoDB
*/
const guestTable = new aws_dynamodb.Table(
this,
"guestTable",
{
partitionKey: {
name: "lineUserId",
type: aws_dynamodb.AttributeType.STRING,
},
billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: RemovalPolicy.RETAIN,
tableName: "Guest",
pointInTimeRecovery: true,
},
);
guestTable.grantReadWriteData(registerGuestUserFn);
}
}
フロントエンド
ユーザーが最初にアクセスするのが以下のような画面だとします。
この画面を開いた瞬間、useEffect
を使用してアクセストークンをバックエンドに送信し、ゲストユーザーを保存するエンドポイントを叩きます。
import { useEffect, useState } from "react";
import liff from "@line/liff";
import "./App.css";
import axios from "axios";
function App() {
// 省略
const registerGuestUser = async () => {
const accessToken = await liff.getAccessToken();
if (accessToken) {
const res = await axios.post(
"https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod", // サーバーサイドへのリクエストエンドポイント
{ accessToken }
);
}
};
+ // アクセス初回時に実行してあげる
+ useEffect(() => {
+ registerGuestUser();
+ }, []);
return (
<div className="App">
<h1>アプリへようこそ!</h1>
<a href="https://xxxxxxxx/">
会員登録はこちらから
</a>
</div>
);
}
export default App;
ちなみに、フロント側でユーザー情報を取得してバックエンドに送信するのではなく、必ずアクセストークンやIDトークンなどを送信するようにしてください。詳しくはこちらの記事で解説しています。
バックエンド
次に、バックエンドを実装します。フロントから送られてきたアクセストークンを使用してユーザー情報を取得し、isMember
ステータスをfalse
としてデータベースに保存します。
以下はLambda関数の例です。
import axios from "axios";
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand, GetCommand } from '@aws-sdk/lib-dynamodb';
// DynamoDBのクライアントの初期化
const dynamoDB = new DynamoDBClient({ region: 'ap-northeast-1' });
const dynamoDBDocumentClient = DynamoDBDocumentClient.from(dynamoDB);
export const handler = async (event: any) => {
const { accessToken } = JSON.parse(event.body);
// LINEユーザー情報を取得
const userInfoResponse = await axios.get("https://api.line.me/v2/profile", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const lineUserId = userInfoResponse.data.userId;
// DynamoDBからユーザーを取得
const guestUser = await dynamoDBDocumentClient.send(new GetCommand({
TableName: 'Guest',
Key: {
lineUserId: lineUserId
}
}));
if (!guestUser.Item) {
// ゲストが存在しない場合、新規作成
+ await dynamoDBDocumentClient.send(new PutCommand({
+ TableName: 'Guest',
+ Item: {
+ lineUserId: lineUserId,
+ lineDisplayName: userInfoResponse.data.displayName,
+ pictureUrl: userInfoResponse.data.pictureUrl,
+ isMember: false // ステータスをfalseにして、未会員ユーザーとして識別できるようにする
+ }
}));
return {
statusCode: 201,
body: JSON.stringify({ message: "新しいユーザー情報を保存しました" })
};
} else {
// ユーザーが既に存在する場合
return {
statusCode: 200,
body: JSON.stringify({ message: "ユーザーは既に登録されています" })
};
}
};
実際にこのアプリにアクセスしてみると、ユーザー情報が保存されていることが確認できます。
これで未会員のユーザー情報を取得できるようになりました。
では、次に会員登録をした際にはこのis_member
ステータスをtrueにして、会員登録済みかどうかをわかるようにします。今回は実装していませんが、すでに会員登録処理がある前提で進めます。
// 会員情報の登録の際に、Guestテーブルの該当ユーザーのisMemberステータスを更新する
await dynamoDBDocumentClient.send(new UpdateCommand({
TableName: 'Guest',
Key: {
lineUserId: userInfoResponse.data.userId
},
UpdateExpression: 'SET isMember = :isMember',
ExpressionAttributeValues: {
+ ':isMember': true
},
ReturnValues: 'UPDATED_NEW'
}));
// 会員情報を登録する処理
// 省略
return {
statusCode: 200,
body: JSON.stringify({ message: "会員情報を保存しました" })
};
};
会員登録をすると同時に、GuestテーブルのisMember
ステータスがtrue
になっていることが確認できます。
このようにすることで、利用ユーザー数が増えた場合に、ユーザーが未会員ユーザーなのか、会員登録済みユーザーなのかを識別することができますね。
そして、未会員ユーザーのみのLINE UIDを用いてオーディエンスを作成し、メッセージを配信するなどのアクションをすることが可能になります。
おわりに
今回、LIFFアプリにおける会員・未会員ユーザーの識別方法について、具体的な実装例を交えて紹介しました。この手法を用いることで、以下のような利点が得られます:
- アプリにアクセスしたすべてのユーザーの情報を取得し、包括的なユーザー分析が可能になる。
- 未会員ユーザーに特化したメッセージ配信や施策を実施できるようになり、会員登録率の向上につながる可能性がある。
- アプリアクセスから会員登録までの過程を追跡できるため、離脱率や転換率などの重要なメトリクスを測定できる。
また、今回はisMember
フラグで会員登録済みかどうかを判断しましたが、単純にGuestテーブルと会員テーブルを照らし合わせるだけでも未会員ユーザーかどうかを判断することは可能です。自身のアプリケーションに応じてこの辺りは柔軟に構成してください。
以上、どなたかの参考になれば幸いです。