見出し画像

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 部分を変更していきましょう。

拡張機能 > Apps Script

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」を選択してください。

新規 > その他 > 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 に定義してください。

ストライプの設定

  1. シークレットキーの発行

下記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つイベント追加

サブスクを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

いいなと思ったら応援しよう!