LIFFアプリにStripe Checkoutを使ってサブスクリプション(定期課金)機能を追加してみる

定期課金をする上でメッセージで訴求したい場面もあるかと思います。そこで、LINEのMessaging APIを使ってステータスに合わせたメッセージを送信もできるので、色々試してみてください!
2021.09.07

みなさんこんにちは、CX事業本部 LINE事業部 PdMのたいがーです🐯

近頃世間に増えてきているサブスクリプションサービスを自分で作ってみたいと思う方も多いかと思います。Stripe Checkoutを使うことで、1回限りの購入、サブスクリプションも支払いページを簡単に作成することができます。

LIFFアプリ上に追加することで、購入時など状況に合わせたメッセージの送信を行うことができます。

そこで今回はStripe Checkoutを使って、LIFF(LINE Front-end Framework)上に決済/定期課金機能を追加していきましょう。

完成後の挙動イメージ

  1. LIFFアプリを起動
  2. 画面内のボタンからStripeから提供されている支払いページへ遷移
  3. ページ内でクレジットカード情報などを入力
  4. StripeのWebhooksを受け取り、決済完了メッセージを公式アカウントから送信
  5. LIFFアプリ上で完了ページを表示

事前準備

LIFF編

LIFFアプリの作成については、以下のブログに掲載されているサンプルアプリのコードを使用させていただきました。LINE Developers上のLINEログインチャネルの作成から詳細に書かれていますので、よろしければご覧ください。

商品の作成

実際にユーザーに購入させたい商品を作成(登録)します。Stripeダッシュボード、Stripe APIどちらからも登録できますが、今回はダッシュボードで作成していきます。

Stripeダッシュボードでログインをした後、上のタブで商品タブに切り替えます。右上の商品を追加ボタンをクリックします。

商品情報入力画面が表示されるので、画像や名前など商品情報を入力していきます。アップロードする画像は2MB以下のJPG/PNG形式のみ可能となっています。

料金体系モデル/課金形態について

Stripeでは料金体系モデルが4種類提供されています。こちらは適宜、請求したい形によって切り替えてください。

  • 標準の料金体系
    • どの場合も同じ単価で請求する場合
  • パッケージ料金体系
    • パッケージ単位で請求する場合
      • 例)5ユニットごとに¥2,500を請求
    • 購入数はデフォルトで切り上げられる
    • 上記例の場合、8ユニット購入する顧客は¥5,000 を支払うことになる
  •  段階的な料金体系
    • 料金について段階を利用している場合
    • 注文内の一部に異なる価格が適用される場合もあり
      • 例)最初の100ユニットについてはユニット当たり¥1,000、以降の50ユニットについてはユニット当たり¥500を請求
  • 数量ベースの料金体系
    • 販売する合計ユニット数に基づく単価で請求する場合
      • 例)50ユニットの場合はユニット当たり¥1,000、100ユニットの場合はユニット当たり¥700を請求

上記体系モデルについては、上二つのモデルのみが継続課金設定が可能になっています。今回は標準の料金体系を選択し、価格設定後、継続を選択します。従量課金制を取り入れることもできますが、今回は設定せずに進みます。

また、請求期間についても週次/月次が選択できますので、お好みのものを選択して下さい。Customer Portalでプラン変更を行う場合、期間によるタブでの切り替えが発生するので、さまざまな期間を追加する場合はそちらの見え方の変化に注意が必要です。

もし複数の商品を追加する場合は右上の保存してさらに追加をクリックし、一度に複数商品を追加することをお勧めします。商品を保存をクリックすると、先程保存した商品の詳細ページが表示されます。こちらのページに記載されているprice IDは後ほど利用します。

LIFFアプリに決済機能を追加する

決済ボタンを追加する

先程掲載したサンプルアプリ Demo App内に決済ボタンを追加していきます。まずはフロント側でPrice IDを指定し、サーバー側に投げる処理を書きます。(StyleSheetは省略しています。)

export default function MembersCard() {
  const [lineUserId, setLineUserId] = React.useState('')
  const { idToken } = React.useContext(AuthContext)
  const { setIsCommunicating } = React.useContext(LoadingContext)

  const handleOnPress = () => {
    const fn = async () => {
      setIsCommunicating(true)
      const res = await demoAPI
        .get('/demo', {
          headers: {
            Authorization: `Bearer ${idToken}`,
          },
          params: {
            priceId: 'price_XXXXXXXXXXXXXXXXX'
          }
        })
        .catch(error => console.dir(error))
      if (res) {
        console.log(res)
        setLineUserId(res.data.lineUserId)
        window.location.href = res.data.url
        console.log(res.data.url)
      }
      setIsCommunicating(false)
    }
    fn()
  }
  
  return (
    <View style={styles.container}>
      <Text style={styles.header}>LINE Demo App</Text>
      <View style={styles.border} />
      <Text style={styles.body}>Welcome!</Text>
      <Text style={styles.body}>ご注文したいメニューをタップしてください。</Text>
      <Button
        title="Pasta"
        onPress={handleOnPress}
      />
    </View>
  )
}

その後、サーバーサイドでCheckoutセッションの作成処理を行っていきます。先程フロントエンド側で指定した Price IDを使用します。Checkoutセッション作成時には、決済成功/失敗時の遷移先の指定/決済モードは指定必須となっています。

export const handler = async (event: LambdaEvent): Promise<APIGatewayProxyResult> => {
  const lineUserId = event.requestContext.authorizer.lineUserId
  const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
  const priceId = event.queryStringParameters!.priceId
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    payment_method_types: ['card'],
    line_items: [
      {
        price: priceId,
        quantity: 1,
      },
    ],
    success_url: 'https://localhost:3000/Success?session_id={CHECKOUT_SESSION_ID}',
    cancel_url: 'https://localhost:3000/',
    client_reference_id: lineUserId
  });
}

payment_method_typesにつきましては、Customer Portalの利用を考えている方は現在Card Typeのみ利用可能ですので注意が必要です。

利用しない場合は以下の全てを検討することができます。複数の支払い方法が渡された場合は、顧客の場所やその他の特性に合わせて動的に並び替えられ、関連度の高い支払い方法が優先されます。

  • alipay
  • card
  • ideal
  • fpx
  • bacs_debit
  • bancontact
  • giropay
  • p24
  • eps
  • sofort
  • sepa_debit
  • grabpay
  • afterpay_clearpay
  • acss_debit
  • wechat_pay
  • boleto
  • oxxo

こちらで決済が可能になりました。

決済完了ページを用意する

次に、先程指定した完了ページを作成していきましょう。components/screens以下にSuccess/index.tsxを作成します。今回はCustomer Portal利用時のことを考え、session IDを取得しています。(もしこの後Customer Portalを使用した構築の予定がなければ、必要のない場合は29~31行目は省いてしまっても大丈夫です!)

import React from 'react'
import { StyleSheet, View, Text, Button } from 'react-native'

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  header: {
    fontSize: 20,
    textAlign: 'center',
    margin: 23,
    fontFamily: FONTS.MAIN_FONT,
    color: COLORS.DIMGRAY,
  },
  border: {
    borderTopWidth: 1,
    borderColor: COLORS.LIGHTGRAY,
  },
  body: {
    fontSize: 12,
    textAlign: 'center',
    margin: 23,
    fontFamily: FONTS.MAIN_FONT,
  },
})


export default function Success() {
  const urlParams = new URLSearchParams(window.location.search)
  const sessionId = urlParams.get('session_id')
  console.log(sessionId)

  return (
    <View style={styles.container}>
      <Text style={styles.header}>LINE Demo App</Text>
      <View style={styles.border} />
      <Text style={styles.body}>Thanks!</Text>
    </View>
  )
}

これで決済完了ページにヘッダーとThanks!が表示されるようになりました!

決済完了メッセージの送信処理を追加する

LIFFアプリを使用しているので、LINEメッセージで決済完了メッセージを公式アカウントから送信してみましょう。ここでは、StripeのCheckoutセッションが完了した際に飛ばされるWebhooksを利用していきます。

Messaging APIチャネルを作成する

LINEでプッシュメッセージを送信するためには、LINE DevelopersコンソールにてMessaging APIチャネルを作成する必要があります。

作成後は一覧から先程作成したMessaging APIチャネルを選択し、Messaging API設定を表示します。

まずは、LambdaでLINEのプッシュメッセージ送信処理を作成していきましょう。

export const handler = async (event: LambdaEvent): Promise<APIGatewayProxyResult> => {
  console.log('event: ', JSON.stringify(event, null, 2))
  const body = JSON.parse(event.body!)
  const lineUserId = body.data.object.client_reference_id
  
  const line = require('@line/bot-sdk');

  const client = new line.Client({
    channelAccessToken: process.env.LIFF_TOKEN
  });

  const message = {
    type: 'text',
    text: 'お買い上げありがとうございました!'
  };
  try{
    await client.pushMessage(lineUserId, message)
  }catch(error){
    console.log(error)
  }
  

  return {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Headers': '*',
    },
    body: JSON.stringify({
    }),
  }
}

backend-stack.tsに以下の2行を追加しましょう。こちらのaddResourceで作成したリソース名は覚えておきましょう。

const sendMessage = api.root.addResource('webhooks')
sendMessage.addMethod('POST', new LambdaIntegration(sendMessageLambda))

その後、AWSリソースをデプロイしていきます。

yarn backend:build
yarn backend:deploy

デプロイ完了後、マネジメントコンソールでAPI Gatewayのページを見ていきます。まずはデプロイがうまく行っているか確認しましょう。

webhooksが追加されていますね。次に左のタブからステージを選択、画面上部にあるURLをコピーします。

続いて、StripeダッシュボードでWebhooksの設定を行っていきます。+ エンドポイントを追加をクリックします。

エンドポイントに先程作成したAPIを追加していきましょう。エンドポイント URLにhttps://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/webhooksを入力します。

次に送信するイベントを選択していきます。今回の場合は支払いに成功した場合にメッセージを送信したいのでcheckout.session.completedを選択します。他にもCheckoutがが期限切れになった場合などもメッセージを送信することができるので、適宜メッセージ内容を切り替えて送信するのも良いでしょう。

デモ

実際にメッセージを送信し、完了ページまで表示します。

最後に

今回はStripeで支払いを行い、LINEのメッセージを送信してみました。ステータスごとにメッセージの送信を切り替えてみると、より良いユーザー体験に繋がりそうですね!

皆さんもどんどんサブスクリプション機能を作成していきましょう!以上、たいがーでした🐯