Stripe UI拡張機能入門: StripeのAPIをアプリ内から呼び出す方法



この記事は、[Stripe Apps を25日間紹介し続ける Advent Calendar 2022](https://qiita.com/advent-calendar/2022/stripe-apps) 8日目の記事です。

![スクリーンショット 2022-11-24 17.56.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/e0261865-0418-daa2-423a-5f4d70558b1b.png)

「Stripe Dashboardを拡張する」ためのアプリを作るには、Stripeのデータを取得・更新・削除する必要があります。

今回は、Stripe AppsのReactアプリ内で、Stripe Node.js SDKを利用したStripe API呼び出しを行う方法を紹介します。

## Stripe Appsアプリは、アプリ内でStripe APIを呼び出せる


## StripeアプリでStripeのAPIを呼び出す方法

今回は、アプリ内からStripe APIを利用して決済履歴を取得するサンプルを構築します。

### Stripe CLIでアプリにAPI呼び出しの権限を設定する

まずはじめに、アプリがどのAPI操作を利用するかの「権限設定」を行います。

アプリケーションのディレクトリ内にて、Stripe CLIで`stripe apps grant permission`を実行しましょう。

#### Step1: 権限を設定したいリソースを選択する

どのリソースへのアクセスかを選択する画面が表示されます。

```bash
$ stripe apps grant permission 
Use the arrow keys to navigate: ↓ ↑ → ← 
? Select a resource to grant access to: 
  ▸ Account link
    Apply Pay Domain
    Application Fee
    Balance
↓   Balance transaction source
```

矢印キーの下を押してスクロールして、`Payment intent`を選択しましょう。

```bash
▸ Payment intent
```

#### Step2: 権限の種類を選択する

「読み込みだけ」か「書き込み（作成・更新・削除）もするか」を聞かれます。

```bash
? Select access type: 
  ▸ read
    write
```

今回は読み込みだけなので、`read`を選びます。

#### Step3: アクセスを要求する理由を入力する

続いて次のメッセージが表示されます。

```bash
✗ Enter a purpose for the grant: █
```

これは、「そのリソースへのアクセス権限を要求する理由」を入力するエリアです。

アプリの設定画面やマーケットプレイスにて、ユーザーが「このアプリはStripeアカウントのどんなリソースに、どういう目的でアクセスしてくるか」を確認できるようになります。

![スクリーンショット 2022-12-06 12.14.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/48ab9aff-18b1-091c-4d38-0c45f45c1068.png)


`Lists the payment intent data to analyze the business.`など、シンプルな説明文を入れましょう。

あとから変更できますので、ローカルで試す範囲であれば、`dummy`や`TBD`などで一旦進めることも可能です。

ただし公開時には修正する必要がありますので、TODOリストに「`stripe-app.json`の`permissions.purpose`を更新する」のようにタスクを入れておくことをお勧めします。

セットアップが終わると、`stripe-app.json`の`permissions`に入力したデータが反映されます。

```diff_json
{
  "id": "com.example.first-stripe-app",
  "version": "0.0.1",
  "name": "First Stripe App",
  "icon": "",
-  "permissions": [],
+  "permissions": [
+    {
+      "permission": "payment_intent_read",
+      "purpose": "tbd"
+    }
+  ],
...
```

#### `stripe apps start`を実行中の場合、アプリの再読み込みが必要

`stripe apps start`を実行したまま`stripe apps grant`コマンドを実行した場合、`stripe apps start`を実行中のターミナルに以下のメッセージが表示されます。

```bash
[14:48:51] compiled successfully
[12:17:16] WARNING stripe-app.json
To reflect recent changes from stripe-app.json, click "Continue" on https://dashboard.stripe.com/apps/enable-preview?cliaccount=acct_123456abcde
[12:17:16] compiled successfully
```

変更した権限設定でアプリを実行するには、このURLにアクセスしてプレビューを再度有効化する必要があります。

![スクリーンショット 2022-12-06 12.21.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/b7676a6a-76b7-fecd-2c94-169be59674d6.png)


### @stripe/ui-extension-sdkで、Stripe Node.js SDKのクライアントをセットアップする

権限の設定が終わりましたので、Stripe APIを呼び出すためのSDKを準備します。

`stripe apps create`で作成したアプリには、デフォルトでNode.js SDKが追加されています。

```json
  "dependencies": {
    "@stripe/ui-extension-sdk": "^8.5.0",
    "stripe": "^11.1.0"
  }
```

Stripe SDKのクライアントは次のコードでセットアップできます。

```typescript
import { createHttpClient, STRIPE_API_KEY } from '@stripe/ui-extension-sdk/http_client'
import Stripe from 'stripe'

// Initiate communication with the stripe client.
const stripe = new Stripe(STRIPE_API_KEY, {
  httpClient: createHttpClient(),
  apiVersion: '2022-11-15',
})
```

:::note warn
`apiVersion`がSDKのバージョンによって変わることがあります。
TypeScriptで型エラーが発生した場合は、エラー文または型定義が指示するバージョンに変更しましょう。
:::

APIキーを環境変数などで設定する必要はありません。

アプリが実行されるサンドボックス側で自動的に設定されます。


### useEffectとuseStateを利用して、API呼び出しとレスポンスの取得を行う

クライアントの準備ができましたので、Reactのコンテキストに従ってAPIを呼び出しましょう。

```tsx
const PaymentIntentsViewer: FC = () => {
  const [paymentIntents, setPaymentIntents] = useState<Stripe.PaymentIntent[]>([])
  useEffect(() => {
    stripe.paymentIntents.list()
      .then(response => {
        setPaymentIntents(response.data)
      })
      .catch(error => {
        console.log(error)
      })
  }, [])
  return (
    <Box>
      {JSON.stringify(paymentIntents, null, 2)}
    </Box>
  )
}
```

作成した`PaymentIntentsViewer`コンポーネントを、`src/views/App.tsx`で読み込ませます。

```diff_typescript

const App = ({ userContext, environment }: ExtensionContextValue) => {
  return (
    <ContextView
      title="Hello world"
      brandColor="#F6F8FA" // replace this with your brand color
      brandIcon={BrandIcon} // replace this with your brand icon
      externalLink={{
        label: "View docs",
        href: "https://stripe.com/docs/stripe-apps"
      }}
    >
+      <PaymentIntentsViewer />    
    </ContextView>
  );
};
```

アプリを再読み込みすると、アカウントのPayment IntentデータがJSONで出力されます。

![スクリーンショット 2022-12-06 12.29.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/6581ccf9-9805-adec-c315-2e619d166017.png)


### List系コンポーネントで、取得したデータを表示する

データの取得ができましたので、見た目を整えましょう。

今回は、`List`コンポーネントを活用します。

https://stripe.com/docs/stripe-apps/components/list

`PaymentIntentsViewer`を次のように書き換えましょう。

```diff_typescript
-import { Box } from "@stripe/ui-extension-sdk/ui";
+import { List, ListItem } from "@stripe/ui-extension-sdk/ui";

// 中略

  return (
+    <List>
-    <Box>  
+      {paymentIntents.map(intent => {
+        const value = `${intent.amount.toLocaleString()} ${intent.currency.toLocaleUpperCase()}`
+        const createdAt = new Date(intent.created * 1000).toLocaleString('ja-JP')
+        return (
+          <ListItem
+            key={intent.id}
+            title={intent.id}
+            secondaryTitle={createdAt}
+            value={value}
+          />
+        )
+      })}
-      {JSON.stringify(paymentIntents, null, 2)}
-    </Box>
+    </List>
  )

```

保存してアプリを再読み込みすると、JSONデータがリスト形式の表示に変わります。

![スクリーンショット 2022-12-06 12.36.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/da88cac2-d84a-7680-3e95-60586ea002bc.png)


## [Advanced] 表示中のページのID情報などを取得する方法

「現在表示中の顧客・サブスクリプションデータを取得したい」など、顧客やサブスクリプションなどのIDが必要なケースも存在します。

この場合は、コンポーネントから渡される`environment`プロパティを利用しましょう。

`environment.objectContext`の中身を見てみます。

```diff_typescript
const App = ({ environment }: ExtensionContextValue) => {
+  console.log(environment.objectContext)
```

顧客の詳細ページでは、次のようなデータが取得できます。

```json
{
  "id": "cus_MuOQ7rso8N3wF6",
  "object": "customer"
}
```

`environment.objectContext.object`でIDの種類を見ることができます。

そして`environment.objectContext.id`でIDを取得し、Stripe APIの引数に渡すことができます。

次のサンプルコードでは、「顧客IDが取得できた場合に、その顧客に関連するPayment Intentを取得する」処理を行います。

```typescript
const App = ({ environment }: ExtensionContextValue) => {
  useEffect(() => {
    if (!environment.objectContext) return
    if (environment.objectContext.object !== 'customer') return
    const customerId = environment.objectContext.id
    stripe.paymentIntents.list({
      customer: customerId
    })
      .then(response => {
        console.log(response.data)
      })
      .catch(error => {
        console.log(error)
      })
  }, [environment.objectContext])
...
```




## ドキュメント

https://stripe.com/docs/stripe-apps/build-ui?locale=ja-JP#access-stripe-objects


## Stripe Appsひとりアドベントカレンダー 2022

今年ベータリリースされたばかりのStripe Appsは、まだ日本語の情報が多くありません。

そこでQiita Advent Calendar 2022にて、毎日Stripe Appsについての情報を投稿します。

https://qiita.com/advent-calendar/2022/stripe-apps

ノーコードで利用する方法や、開発するためのTipsなども紹介予定ですので、ぜひ購読をお願いします。