LIFFとStripe.jsを組み合わせて、決済完了後にLINEでメッセージを送信できるようにする


この記事は、[🌟LINE DC🌟 LINE Developer Community Advent Calendar 2022](https://qiita.com/advent-calendar/2022/linedc) 11日目の記事です。

Stripe.jsとLIFF SDKの２つを活用することで、顧客とのコミュニケーションと決済をReactなどのアプリ内で表現できます。

## プロジェクトとライブラリのセットアップ

まずはReactアプリのセットアップと、依存するライブラリを準備しましょう。

### ViteでReactアプリを立ち上げる

Viteを利用して、Reactアプリをセットアップします。

```bash
$ yarn create vite liff-stripe --template react
```

### LIFFとLIFF向けReactライブラリを追加する

続いてLIFFを組み込むためのライブラリを追加します。

```bash
$ yarn add @line/liff react-liff　@line/liff-mock
```

今回、Reactへの組み込みには、以下のライブラリを使用しています。

https://github.com/epaew/react-liff

### Stripe.jsライブラリを追加する

続けてStripe Elementsで使用するStripe.js系ライブラリも追加します。

```bash
$ yarn add @stripe/stripe-js @stripe/react-stripe-js 
```

### 環境変数を設定する

StripeとLIFFで使用するAPIキーなどを環境変数として設定します。

Viteの場合、`VITE_`から始めましょう。

```env
VITE_LIFF_ID=123456789-abcdefg
VITE_STRIPE_PUBLISHABLE_API_KEY=pk_test_xxx
```

## LIFFのセットアップ

LIFFをReactアプリに組み込みましょう。

`src/main.tsx`を次のように書き換えます。

```tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { LiffProvider } from 'react-liff'
import { LiffMockPlugin } from '@line/liff-mock'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <LiffProvider
      liffId={import.meta.env.VITE_LIFF_ID}
      plugins={[
        new LiffMockPlugin()
      ]}
      // @ts-ignore
      mock={true}
    >
      <App />
    </LiffProvider>
  </React.StrictMode>
)
```

続いて、ユーザー名を表示させましょう。

`src/App.tsx`を次のように変更します。

```tsx
import { useEffect, useState } from 'react'
import './App.css'
import { useLiff } from 'react-liff'

function App() {
  const [username, setUsername] = useState("")
  const { isLoggedIn, liff } = useLiff()
  useEffect(() => {
    (async () => {
      if (!liff.isLoggedIn()) {
        liff.login()
      }
      const profile = await liff.getProfile();
      setUsername(profile.displayName)
    })();
  }, [liff, isLoggedIn]);

  return (
    <div className="App">
      <h1>Hello {username}</h1>
    </div>
  )
}

export default App

```

`Hello Brown`と画面に出れば成功です。

![スクリーンショット 2022-12-06 21.03.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/0f94fb05-ea56-ba72-3506-287a523b6e97.png)

`src/main.tsx`の`mock={true}`を削除すると、LINEログイン画面に遷移します。

```diff_typescript
-      // @ts-ignore
-      mock={true}
```

## Stripe Elementで決済フォームを表示する

ここからはStripe.jsを利用して、決済フォームをLIFFアプリに追加します。

### クイックスタートを利用して、　Payment Intentを用意するAPIを作成する

Stripe.jsのカード要素を表示するには、事前にPayment IntentまたはSetup Intent（カード情報などの保存のみする場合）を作成します。

StripeドキュメントのクイックスタートなどでAPIを用意しましょう。

https://stripe.com/docs/payments/quickstart

### APIを呼び出して、Payment Intentを取得する

作成したAPIを呼び出して、アプリ側でPayment IntentのClient Secretを取得しましょう。

```diff_typescript
  }, [liff, isLoggedIn]);

+  const [paymentIntentClientSecret, setPIClientSecret] = useState("")
+  useEffect(() => {
+    fetch("http://localhost:3000/create_payment_intent", {
+      method: "POST"
+    }).then(data => data.json())
+      .then(data => setPIClientSecret(data.clientSecret))
+  }, [])

  return (
    <div className="App">
```

### 取得したPayment Intentで、決済フォームを表示する

続いてStripe.js用のProviderにClient Secretを渡します。

```diff_typescript
import { useLiff } from "react-liff"
+import { loadStripe } from "@stripe/stripe-js"
+import { Elements } from "@stripe/react-stripe-js"

function App() {

...

  return (
    <div className="App">
      <h1>Hello {username}</h1>
+      {paymentIntentClientSecret ? (
+        <Elements
+          stripe={loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_API_KEY)}
+          options={{
+            clientSecret: paymentIntentClientSecret
+          }}
+        >
+          
+        </Elements>
+      ): null}
    </div>
  )
```

Providerを用意しましたので、子コンポーネントを作成してフォームを表示させましょう。

```tsx
import { PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js"

function PaymentForm() {
  const stripe = useStripe()
  const elements = useElements()
  return (
    <form
      onSubmit={async e => {
        e.preventDefault()
        if (!stripe || !elements) return;
        const result = await stripe.confirmPayment({
          elements,
          redirect: 'if_required',
        })
        console.log(result)
      }}
    >
      <PaymentElement />
      <button type="submit">Buy</button>
    </form>
  )
}
```

このコンポーネントを、`Elements`の子要素で配置すればOKです。

```diff_typescript

        <Elements
          stripe={loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_API_KEY)}
          options={{
            clientSecret: paymentIntentClientSecret
          }}
        >
+          <PaymentForm />
        </Elements>
```

## 決済完了後に、LIFFでメッセージを送信する

決済が完了したタイミングで、LIFFの`sendMessages`を利用してメッセージを送信できます。

　`PaymentForm`コンポーネントを次のように変更しましょう。

```diff_typescript
function PaymentForm({ displayName }: { displayName?: string}) {
  const stripe = useStripe()
  const elements = useElements()
+  const { liff } = useLiff()
  return (
    <form
      onSubmit={async e => {
        e.preventDefault()
        if (!stripe || !elements) return;
        const result = await stripe.confirmPayment({
          elements,
          redirect: 'if_required',
        })
+        await liff.sendMessages([{
+          type: "text",
+          text: `Your payment id: ${result.paymentIntent?.id}`
+        }])
      }}
    >
      <PaymentElement />
      <button type="submit">Buy</button>
    </form>
  )
}
```

このように、Stripe.jsとLIFFを組み合わせることで、Reactアプリ内で決済とメッセージングを連携することができます。