LIFFアプリ上にStripeの支払いボタンを簡単に設置できるLIFFプラグインを作ってみた

# 爆誕 LIFFプラグイン
最近、LIFFのアップデートがあり **LIFFプラグイン** という新しい機能が追加されました✨
面白そうな新機能だったので、簡単なプラグインを作ってみました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">LIFF (LINE Front-end Framework) v2.19.1をリリースしました。LIFF v2.19.1では、LIFF SDKを拡張できるLIFFプラグイン機能を追加しました。 <a href="https://twitter.com/hashtag/LINE_API?src=hash&amp;ref_src=twsrc%5Etfw">#LINE_API</a> <a href="https://twitter.com/hashtag/LIFF?src=hash&amp;ref_src=twsrc%5Etfw">#LIFF</a> <a href="https://twitter.com/hashtag/LINE%E3%83%9F%E3%83%8B%E3%82%A2%E3%83%97%E3%83%AA?src=hash&amp;ref_src=twsrc%5Etfw">#LINEミニアプリ</a><br>詳しくはこちら：<a href="https://t.co/pXy3MsqHfP">https://t.co/pXy3MsqHfP</a></p>&mdash; LINE Developers (@LINE_DEV) <a href="https://twitter.com/LINE_DEV/status/1515963216740110342?ref_src=twsrc%5Etfw">April 18, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# LIFFプラグインとは
[LINE社公式のドキュメント](https://developers.line.biz/ja/docs/liff/liff-plugin/) では以下のように説明されています。
>LIFFプラグインとは、LIFF SDKを拡張できる機能です。LIFFプラグインを使うと、LIFF SDKに独自のAPIを追加したり、LIFF APIの挙動を変更したりできます。

どうやらLIFF SDKに独自の機能を付け加えることができるようになり、かつその独自機能をプラグインという形で他の開発者にも共有できる、ということのようです。

簡単にいうと、LIFF SDKがあって、LIFFプラグインを追加すると独自の機能を使えるようにLIFFが進化します。そして、イラストの「剣」の部分は他の人にも共有できる！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594998/e0105c7d-4e6d-378b-32c4-b4703209ca50.png)



超すばらしいアップデートだと思います。LINEのエンジニアの皆様ありがとうございます。

# 作ってみる
LIFFプラグインは、`name`プロパティと`install()`メソッドを持つ、オブジェクトまたはクラスとして作成できます。
`install()`メソッドは、`liff.use()`したときに呼び出され、独自のAPIをliffオブジェクトに追加します。

今回、**LIFFアプリにStripeの決済ボタンを簡単に設置できるプラグイン**を作ってみました。
一応、ソースコードを載せますがプラグインを使うだけであれば中身は気にしなくて大丈夫です。

```Javascript: plugin.js
class StripeCheckoutButtonPlugin {

  // コンストラクタ、nameを設定する
  constructor() {
    this.name = 'stripeCheckoutButton';
  }

  // 独自のAPIを追加する、liff.use()したときに呼ばれる
  install() {
    return {
      init: this.init,
      bind: this.bind,
    };
  }

  // Stripeの公開可能キーをインスタンスに保持する
  init(obj) {
    if (obj.apiKey === undefined || obj.apiKey.length === 0) {
      console.log('API Key is required to initialize');
      return;
    }
    if (!obj.apiKey.startsWith('pk_')) {
      console.log('Invalid Publishable Key');
      return;
    }
    this.stripePublishableKey = obj.apiKey;
  };

  // ボタンなどの要素にCheckoutに画面遷移するイベントリスナーを追加する
  bind(elem, params) {
    if (!checkRequiredParams(params)) {
      return;
    }
    elem.addEventListener('click', () => {
      let option = {
        lineItems: [{ price: params.priceId, quantity: params.quantity ?? 1 }],
        mode: params.mode ?? 'payment',
        successUrl: params.successUrl,
        cancelUrl: params.cancelUrl,
        locale: params.locale ?? 'ja',
        submitType: params.submitType ?? 'auto'
      };
      if ('clientReferenceId' in params) {
        option.clientReferenceId = params.clientReferenceId;
      }
      if ('customerEmail' in params) {
        option.customerEmail = params.customerEmail;
      }
      if ('billingAddressCollection' in params) {
        option.billingAddressCollection = params.billingAddressCollection;
      }
      if ('shippingAddressCollection' in params) {
        option.shippingAddressCollection = params.shippingAddressCollection;
      }
      Stripe(this.stripePublishableKey, {
        apiVersion: '2020-08-27'
      }).redirectToCheckout(option);
    });

    // validate
    function checkRequiredParams(obj) {
      if (obj.priceId === undefined) {
        console.log('PriceId is required');
        return false;
      }
      if (!obj.priceId.startsWith('price_')) {
        console.log('Invalid PriceId');
      }
      if ('quantity' in obj && obj.quantity < 0) {
        console.log('Invalid Quantity');
      }
      if (obj.successUrl === undefined) {
        console.log('SuccessUrl is required');
        return false;
      }
      if (obj.cancelUrl === undefined) {
        console.log('CancelUrl is required');
        return false;
      }
      return true;
    }
  };
}
```

# プラグインの使い方
せっかくですので、このプラグインの使い方を紹介させていただきます。
LIFFを有効化したアプリの中で以下の手順で使えます。ぜひ試してみてください。
#### 1. LIFF SDK、Stripeのクライアント用ライブラリ、プラグインを読み込む
ドキュメントファイルの`body`部分で3つのJSファイルを読み込みます。
```HTML: index.html
<script src="https://js.stripe.com/v3"></script>
<script src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
<script src="https://cdn.jsdelivr.net/gh/kota-imai/liff-plugin-stripe-checkout-button@main/v1/plugin.min.js"></script>
```
#### 2. ボタン要素を追加する
ドキュメント上に適当にボタン要素を追加します。

```HTML: index.html
<button id="checkout-button"></button>
```

#### 3. liff.use()でプラグインを追加する
```Javascript:
liff.use(new StripeCheckoutButtonPlugin());
```

#### 4. Stripeの公開可能キーで初期化
Stripeの公開可能キーを使って初期化します。公開可能キーは[Stripeのダッシュボード](https://dashboard.stripe.com/test/apikeys)から確認できます。
```Javascript: 
liff.$stripeCheckoutButton.init({
   apiKey: 'pk_XXXXXXXXXXXXXXXXXXXXXX' //Stripeの公開可能キー
});
```

#### 5. ボタン要素にバインド
ボタン要素にCheckoutに遷移するイベントリスナーを追加します。`bind()`は第一引数に対象のボタン要素、第二引数に決済画面を作る際に必要な情報をオブジェクト型で渡します。


```Javascript:
const button = document.getElementById("checkout-button");
liff.$stripeCheckoutButton.bind(button, {
    priceId: 'price_XXXXXXXXXX', // Stripeの商品の価格ID
    successUrl: `${location.origin}/success?id={CHECKOUT_SESSION_ID}`, // 決済成功時に遷移するURL
    cancelUrl: `${location.origin}/cancel`, // 決済キャンセル時に遷移するURL
});
```

:::note
`liff.$stripeCheckoutButton.bind()`の第二引数について、以下の値は必須項目です。
- **priceId** ... Stripeの商品の価格ID、Stripeのダッシュボードから確認できます
- **successUrl** ... 決済成功時に遷移する先のURL、サンキューページ
- **cancelUrl**  ... 決済をキャンセルした時に遷移する先のURL

そのほかのオプションは[GitHubリポジトリのREADME](https://github.com/kota-imai/liff-plugin-stripe-checkout-button#--%E6%94%AF%E6%89%95%E3%81%84%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AE%E8%A8%AD%E7%BD%AE)をご参照ください。
:::

.
HTMLの記述例です。JSも一緒に入れてます


```diff_html: index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>LIFF Stripe Checkout</title>
</head>

<body>
+ <script src="https://js.stripe.com/v3"></script>
+ <script src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
+ <script src="https://cdn.jsdelivr.net/gh/kota-imai/liff-plugin-stripe-checkout-button@main/v1/plugin.min.js"></script>
  
+ <button
+   style="background-color:#6772E5;color:#FFF;padding:8px 12px;border:0;border-radius:4px;font-size:1em;cursor:pointer"
+   id="checkout-button" type="button">
+   Stripeで支払う
+ </button>

  <script>
+   liff.use(new StripeCheckoutButtonPlugin());
    liff.init({
      liffId: '{LIFF_ID}'
    })
      .catch((err) => {
        console.log(err);
      });
    liff.ready.then(() => {
      if (!liff.isLoggedIn()) {
        liff.login();
      }
      const idToken = liff.getDecodedIDToken();
      const userId = idToken.sub;
      const email = idToken.email;

      const button = document.getElementById('checkout-button');
+     liff.$stripeCheckoutButton.init({
+       apiKey: 'pk_XXXXXXXXXX{Stripeの公開可能キー}'
+     });
+     liff.$stripeCheckoutButton.bind(button, {
+       priceId: 'price_XXXXXXXXX{Stripeの商品の価格ID}',
+       successUrl: `${location.origin}/success?id={CHECKOUT_SESSION_ID}`,
+       cancelUrl: `${location.origin}/cancel`,
+       clientReferenceId: userId,
+       customerEmail: email
+     });
    });
  </script>
</body>
</html>
```


-> [デモアプリを開く](https://liff.line.me/1655194342-9oALq1yA)
-> [詳細](https://github.com/kota-imai/liff-plugin-stripe-checkout-button)


# 決済後に顧客にプッシュメッセージを送りたいときは
Messaging APIとStripeのWebhookを組み合わせて使うことで実現可能です。
少々複雑なので、ここでの説明は割愛します🙇‍♂️

# まとめ
LIFFアプリの機能を簡単に拡張・共有できて便利ですね。色々なLIFFプラグインが誕生して、LIFF x ?? のサービス連携がより一層加速していきそうな予感です。

なお、プラグインの共有方法についてはまだユースケースもなく、ベストプラクティスが分かりません。この辺について、LINE社からガイダンスがあると嬉しいのですが。。

最後まで読んで頂きありがとうございました。
記事の内容についてご質問などがあればご連絡ください。
-> [Twitter](https://twitter.com/kaonaga9)


# 参考

https://developers.line.biz/ja/docs/liff/liff-plugin/

https://stripe.com/docs/payments/checkout/client