LINEだけであなたの商品をサブスクリプション販売しませんか?
Stripeのサブスク機能をスプレッドシート×GASでLINEBotに実装
Googleスプレッドシートをデータベースに、Stripe × GASというとっつきやすい開発ツールでLINEで単品決済できる機能の実装方法です!
YouTube LIVE:https://www.youtube.com/watch?v=y-z5monAe1A
続きの配信(2023/11/3):https://youtu.be/FINqq_6wrcQ
※続きの配信では、LIVE中の質問で答えられなかったことやできなかったこと、またサブスクだけでなく単品決済の実装について配信しました。(29分)
事前準備
Stripeのアカウントをお持ちでない方は事前にユーザ登録をお願いします!
スプレッドシートは1つですが、Apps Script は2つ作成していきます。
LINE bot 作成用のプログラム
LINE bot を既にお持ちの場合は、こちらの「LINE bot 作成用のプログラム」部分はスキップしてください。
まだLINE bot をお持ちでない方は、下記のスプレッドシートをクリックしてコピーしてご活用ください。https://docs.google.com/spreadsheets/d/1fWGtuWSlL3jzhfj0EtMeTBzei-yv3Vv-jQ07oqHy3hE/copy
コピーしたら、上のメニューバーの「拡張機能」から「Apps Script」を作成していきます。既にプログラムは作成済みなので、設定.gs 部分を変更していきましょう。
ChatGPT の API でできることやプログラムについてもっと知りたい場合は、下記の note をご参考ください。
LINE Developers で LINE Bot 作成
LINE Developers にログインしたら、まずはプロバイダーを作成し、新規チャネル「Messaging API」を作成します。
チャネルを作成したら、「Messaging API設定」の一番下にある「チャネルアクセストークン」を発行して、設定.gs のプログラムの5行目に定義してください。
画面一番上に戻ってチャネル基本設定をクリックし、LINE Official Account Manager を開いてください。別タブで新しくウィンドウが立ち上がります。
応答設定の応答機能は、下記のように設定してください。
チャット:オフ
あいさつメッセージ:オフ
Webhook:オン
応答メッセージ:オフ
そうしましたら、LINE Official Account Manager のタブは閉じてOKです。
GASの画面に戻り、右上の青いボタン「デプロイ」から「新しいデプロイ」を選択します。歯車マークが左上にあるので、そのボタンをクリック。「ウェブアプリ」を選択。アクセスできるユーザーは「全員」にして、右下の青いボタン「デプロイ」を押します。
「アクセスを承認」が聞かれるので、承認してください。
ウェブアプリのURLが発行できたらコピーして、LINE Developers のページのチャネルアクセストークンを発行した少し上の方に「Webhook設定」があると思います。
Webhook URLを設定して、Webhookの利用は「オン(緑色)」にしてください。QRコードをLINEに読み込み、おうむ返しで返事がくれば成功です。
OPEN AI社のAPIをお持ちの方は、APIキーを設定.gs 4行目に定義すれば、LINEでChatGPTが使用できるようになります。
ストライプ決済のプログラム
Googleドライブからしたら、「新規」の「その他」から「Google Apps Script」を選択してください。
2つのスクリプトファイルを使用します。「コード.gs」と「設定.gs」として、それぞれ下記のプログラムを貼り付けてください。
コード.gs
// Stripeのサブスクリプション登録情報とデータベースの整合性をとる https://stripe.com/docs/api/subscriptions
function checkSubscriptionStatus() {
const options = {
headers: {
Authorization: "Bearer " + STRIPE_SECRET,
},
method: "get",
muteHttpExceptions: true
};
for(i = 1; i < SUBSCRIBE_USER_ROW_NUM; i++) {
const url = `https://api.stripe.com/v1/subscriptions/${SUBSCRIBE_USER_VALUES[i][3]}`;
const userId = SUBSCRIBE_USER_VALUES[i][0];
const response = UrlFetchApp.fetch(url, options); // レスポンス取得
const subscription = JSON.parse(response.getContentText()); // ステータス取得
const status = subscription.status || "error";
const canseleStatus = subscription.cancel_at_period_end;
const cell = SUBSCRIBE_USER_RANGE.getCell(i + 1, 5); // Subscription Status
let result;
if(status != "active") {
result = "inactive"; // 解約済み or 何か問題があったらステータスを inactive に変更
} else if (canseleStatus) {
result = "canceled"; // cancel_at_period_end が true の時 canceled に変更
} else {
result = "active";
}
cell.setValue(result); // 解約済み or 何か問題があったらステータスを inactive に変更
userSheetUpdate(userId, result);
}
}
// Stripeのwebhook
function doPost(e) {
let body = JSON.parse(e.postData.contents);
if(body.type) {
const eventType = body.type; // Webhookの種類を判別
const data = body.data.object; // 取得したデータの中身
if (eventType == "checkout.session.completed")
createSubscription(data) // 支払いが完了した場合の処理
if (eventType == "customer.subscription.updated")
updateSubscription(data); // サブスクリプションステータスが変更された場合の処理
}
}
// 支払いが完了した場合の処理
function createSubscription(data) {
const customerId = data.customer;
const subscriptionId = data.subscription;
const date = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd HH:mm:ss");
// PAYMENT LINK が正しいかどうか
const pLink = data.payment_link;
if (!PAYMENT_LINKS.includes(pLink))
return;
// 既にユーザーが存在しているかどうかチェック
for(i = 0; i < SUBSCRIBE_USER_ROW_NUM; i++)
if((SUBSCRIBE_USER_VALUES[i][2] == customerId && SUBSCRIBE_USER_VALUES[i][3] == subscriptionId))
return;
const lineId = data.client_reference_id;
const email = data.customer_details.email;
// 単品決済の時
// if (!subscriptionId) {
// KESSAI_SHEET.appendRow([lineId, email, pLink, date]);
// sendPushMessage(lineId, "単品決済完了!", LINE_ACCESS_TOKEN);
// return;
// }
// サブスク決済の時
SUBSCRIBE_USER_SHEET.appendRow([lineId, email, customerId, subscriptionId, "active", date, date, "", pLink]);
userSheetUpdate(lineId, "active"); // スプレッドシート「ユーザー」更新
return;
}
// スプレッドシート「ユーザー」更新
function userSheetUpdate(userId, status) {
const date = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd HH:mm");
const row = findUserRow(userId);
const column = 7; // 課金更新日時
const val = USER_SHEET.getRange(row, column).getValue();
const statusColumn = 9; // ステータスをactiveに変更
USER_SHEET.getRange(row, column).setValue(date);
let message;
if (!val) {
message = `登録ありがとう!\n登録状況に関しては、下記のリンクから確認してね!\n${PORTAL_SITE}`;
sendPushMessage(userId, message, LINE_ACCESS_TOKEN);
}
USER_SHEET.getRange(row, statusColumn).setValue(status);
return;
}
// ユーザーの行を取得
function findUserRow(userId) {
const textFinder = USER_SHEET.createTextFinder(userId);
const ranges = textFinder.findAll();
const row = ranges[0].getRow();
return (row);
}
// サブスクリプションステータスが変更された場合の処理
function updateSubscription(data) {
const customerId = data.customer;
const subscriptionId = data.id;
const cancel_check = data.cancel_at_period_end;
const date = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd HH:mm:ss");
for(i = 0; i < SUBSCRIBE_USER_ROW_NUM; i++) {
if((SUBSCRIBE_USER_VALUES[i][2] == customerId && SUBSCRIBE_USER_VALUES[i][3] == subscriptionId)) {
let statusCell = SUBSCRIBE_USER_RANGE.getCell(i + 1, 5); // 5行目 Subscription Status
let updateDateCell = SUBSCRIBE_USER_RANGE.getCell(i + 1, 6); // 6行目 updated_at
let cancelDateCell = SUBSCRIBE_USER_RANGE.getCell(i + 1, 8); // 8行目 canceled_at
if (cancel_check) {
statusCell.setValue("canceled");
cancelDateCell.setValue(date);
} else {
statusCell.setValue("active");
}
updateDateCell.setValue(date);
break;
}
}
}
// LINE Push Message
function sendPushMessage(to, pushText, line_token) {
const postData = {
"to" : to,
"messages" : [
{
"type" : "text",
"text" : pushText
}
]
};
return postMessage(postData, line_token, LINE_PUSH_URL);
}
function postMessage(postData, line_token, api_url) {
const headers = {
"Content-Type" : "application/json; charset=UTF-8",
"Authorization" : `Bearer ${line_token}`
};
const options = {
"method" : "POST",
"headers" : headers,
"payload" : JSON.stringify(postData)
};
return UrlFetchApp.fetch(api_url, options);
}
設定.gs
// Stripeの設定
// テスト環境
const STRIPE_SECRET = 'sk_test_****************';
const PORTAL_SITE = `https://billing.stripe.com/p/login/test_****************`;
const PAYMENT_LINKS = [`plink_****************`];
// 本番環境
// const STRIPE_SECRET = 'sk_live_****************';
// const PORTAL_SITE = `https://billing.stripe.com/p/login/****************`;
// const PAYMENT_LINKS = [`plink_****************`];
// スプレッドシートとの連携部分
const SHEET_ID = "*********************************";
const USER_SHEET = SpreadsheetApp.openById(SHEET_ID).getSheetByName("ユーザー");
// const KESSAI_SHEET = SpreadsheetApp.openById(SHEET_ID).getSheetByName("単品決済"); // 単品決済用
const SUBSCRIBE_USER_SHEET = SpreadsheetApp.openById(SHEET_ID).getSheetByName("Stripeユーザリスト");
const SUBSCRIBE_USER_ROW_NUM = SUBSCRIBE_USER_SHEET.getLastRow();
const SUBSCRIBE_USER_RANGE = SUBSCRIBE_USER_SHEET.getRange(1, 1, SUBSCRIBE_USER_ROW_NUM, 9);
const SUBSCRIBE_USER_VALUES = SUBSCRIBE_USER_RANGE.getValues();
// LINEとの連携部分
const LINE_PUSH_URL = "https://api.line.me/v2/bot/message/push"; // LINEユーザー個別に送る
const LINE_ACCESS_TOKEN = "*********************************";
スプレッドシートの設定
スプレッドシートのURL部分には、唯一無二の ID となっている部分があります。それを、12行目 SHEET_ID に定義してください。
ストライプの設定
シークレットキーの発行
下記URLからシークレットキーをコピーして、STRIPE_SECRET に定義してください。
テスト環境:https://dashboard.stripe.com/test/apikeys
本番環境:https://dashboard.stripe.com/apikeys
2. ポータルサイトの発行
次に、下記URLからカスタマーポータルリンクを発行後、URLをコピーして、PORTAL_SITE に定義してください。
テスト環境:https://dashboard.stripe.com/test/settings/billing/portal
本番環境:https://dashboard.stripe.com/settings/billing/portal
3. ペイメントリンクの発行
次に、下記URLの右上「商品を追加」からサブスクリプションの商品を保存します。
テスト環境:https://dashboard.stripe.com/test/products
本番環境:https://dashboard.stripe.com/products
保存後、商品画面も右下辺りにある「支払いリンクを表示」をクリックし、URLの plink_ 以降をコピーして、PAYMENT_LINKS に定義してください。ちなみに、この画面にある支払いリンクから、実際にクレジットカード番号を「4242 4242 4242 4242」にしてテスト決済することができます。
それから、LINE ACCESS TOKEN もこちらの設定.gs にも定義してください。決済完了後に、プッシュ通知で購入した旨をLINEに送信します。
これで、プログラムは完成です。
デプロイ
Stripe側のGASをデプロイ
Apps Script 画面の右上にある「デプロイ」から、URLを発行できます。
発行したURLを、ストライプの開発者ページにある Webhook のエンドポイントに追加します。
テスト環境:https://dashboard.stripe.com/test/webhooks
本番環境:https://dashboard.stripe.com/webhooks
また、送信イベントには、下記2つのイベントを追加してください。
サブスクを2つ以上作成する際は、ペイメントリンクのID部分を配列に追加してください。
LINEbot側のGASを再デプロイ
支払いリンクをコピーして、LINEbot側の方のGASの設定.gs 21行目 STRIPE_SUBSCRIPTION_URL_ONE に、https://google.com となっているのを消して、貼り付けてください。
これでもう一度デプロイしなおすのですが、今回は「新しいデプロイ」ではなく「デプロイを管理」を選択してください。
次にポップアップが出てくるので、右上の鉛筆マークをクリックして「新バージョン」としてデプロイします。こうすることで、Webhook URL を変えずにデプロイし直すことができます。
では、3回以上メッセージを送ってみて、左下の「サブスク①」からテスト決済してみてください。クレジットカード番号は「4242 4242 4242 4242」で入力ください。スプレッドシートの「ユーザー」と「Stripeユーザリスト」に決済の情報が記載され、メッセージが3回以上でも「今日はここまで!もっとお話する?」と言われず、送り続けられるようになっていれば成功です。
注意点
クイズでもありましたが、動画内では解説していませんでしたが、今回はLINEのプッシュメッセージを使っています。無料枠のプッシュメッセージは、月に200通までしか送ることができないので、ここだけ注意が必要です。お気をつけください!
トリガー設定
LINEbot側のトリガー
refreshTodaysTime という関数をLINEbot側のGASの push.gs の中に入れています。これは、毎日全員の今日の投稿数を0に戻すプログラムです。これで、無料枠の人でも毎日3回まで投稿ができるようになります。
GAS画面の左側の時計マーク「トリガー」から、右下の「トリガーを追加」をクリックします。
実行する関数を選択:refreshTodaysTime
実行するデプロイを選択:head
イベントのソースを選択:時間手動型
時間ベースのトリガーのタイプを選択:日付ベースのタイマー
時刻を選択:午前0時〜1時
ここまでできたら保存して、明日スプレッドシートの投稿数が0になっているか確認してみてください。
Stripe側のトリガー
LIVE中には解説しませんでしたが、checkSubscriptionStatus という関数もトリガー用に追加しています。この関数では、サブスクリプション登録情報とスプレッドシートの整合性をとることができ、キャンセルした人が非アクティブ状態になったりできるので、設定しておいてください。
実行する関数を選択:checkSubscriptionStatus
実行するデプロイを選択:head
イベントのソースを選択:時間手動型
時間ベースのトリガーのタイプを選択:日付ベースのタイマー
時刻を選択:午前0時〜1時
以上。お疲れ様でした!
追記(2023年11月3日 18時)
LIVE中の質問で答えられなかったことや、できなかったこと、またStripeのサブスクリプションだけでなく、単品決済についても解説した動画(29分)を作成しましたので、合わせてご確認いただけると幸いです。記事中のプログラムも、下記の動画に合わせて全て変更済みです。
PR
YouTube『仲条高幸 Tech School for Change Makers』
GAS や LINE に関してはもちろん、テクノロジー全般的に幅広く気ままに運営しています。毎週金曜18時に動画を更新しております。ぜひチャンネル登録していただけると嬉しいです!
LINE結婚相談所『Liebe(リーベ)』
LINEだけで入会できる結婚相談所です。全てオンライン完結なので、国内最安値で婚活を始めることができるサービスです。もちろん、決済は Stripe を使用しています。まずは「恋人診断」だけでも楽しいと思うので、お気軽に友だち登録してください。
Udemy『ChatGPT API で LINE BOT』
ChatGPT を使用したLINE公式アカウントに興味がある人におすすめの講座です。
▼クーポンコード記載してますのでご活用ください↓↓↓
クーポンコードは「年月」を変更して入力
例)2023年10月の場合 → 202310