Stripeの料金データを通貨やmetadataなどで検索・取得する6つの方法

Stripeでは、一回切りの決済でも定期課金でも商品・料金データをDashboardで登録して利用します。

Checkoutのサンプルコードでも、料金IDを引数に設定していますね。

```js
  const sesssion = await stripe.checkout.sessions.create({
    mode: 'subscription',
    line_items: [{
      price: 'price_xx',
      quantity: 1
    }]
  ...
  })
```

そのため、ECやSaaSなどで、複数の商品・料金をユーザーが選んで決済できるシステムを作りたい場合には、料金データのリストを取得して表示させる実装が便利です。

ただし、提供する製品やサービスによっては、「取得したい料金」と「除外したい料金」が発生します。

ここでは、料金データをStripeのAPIから取得する方法を6種類紹介します。

## 基本: Price APIのListメソッドを利用して取得する

まず基本の形として、Price APIでListメソッドを利用する方法を紹介します。

```js
const prices = await stripe.prices.list();
```

引数を何も設定していない場合、有効化されている料金データを先頭１０件分取得できます。

一部の方法を除いて、基本的にはこのAPIに引数を渡して、料金データを検索します。

## 検索方法1: 通貨や有効化ステータス、支払いサイクルで検索する

List APIでは、通貨や有効化ステータス・支払いサイクルでの検索ができます。

そのため、「日本円の定期課金料金を３０件」や「月額の定期課金データを最大100件」のような、検索が可能です。

### 日本円の料金データのみ取得する

```js
await stripe.prices.list({
  currency: 'jpy',
});

```

### 月額のサブスクリプション料金のみ取得する

```js
await stripe.prices.list({
  recurring: {
    interval: 'month',
  },
  type: 'recurring',
});
```

## 検索方法2: 特定の商品に紐付けされた料金データを取得する

List APIには、「どの商品に紐付けされた料金か」を検索するためのオプションも含まれています。

`product`パラメーターに対象の商品IDを入れることで、その商品に設定された料金のみが取得できます。

```js
const prices = await stripe.prices.list({
  product: 'prod_xxx',
});
```

また、その他のパラメーターと組み合わせることもできますので、「`prod_xxx`の日本円での買い切り料金のみ取得」などの検索も可能です。

```js
const prices = await stripe.prices.list({
  product: 'prod_xxx',
  currency: 'jpy',
  type: 'one_time',
});
```

## 方法3: [検索キー]を利用して、必要な料金データのみ取得する

「表示させたい料金があまり多くない」かつ「一部の料金は、価格改訂を行う可能性がある」ケースでは、「検索キー(Lookup Key)」を利用することもできます。

`lookup_keys`引数で取得したい料金の検索キーを列挙することで、対応する検索キーが設定されている料金データのみを取得できます。

```js
const result = await stripe.prices.list({
  lookup_keys: ['membership_free', 'membership_bronze', 'membership_silver']
})
```

検索キーを利用すると、価格改訂で新しい料金データを表示させたい場合の作業量を減らすことができます。
`transfer_lookup_key`を利用したCreateまたはUpdateのAPIリクエストを実行するだけで、取得・検索側の実装を変更せずに料金データを差し替えることができます。

```js
await stripe.prices.update('price_xxxx', {
  transfer_lookup_key: true,
  lookup_key: 'monthly_membership_bronze',
})
```

現時点では、取得したいキーを明示的に指定する必要があります。
そのため、Connectを利用したマーケットプレイスなど、料金データが増減しやすいシステムでは実装にすこし工夫が必要です。

### 検索キーの使い方についてはこちらの記事へ

https://qiita.com/hideokamoto/items/fed4a7caba36585dccee

## 方法4: Search APIを利用する

検索キーやList APIのパラメーターだけでは検索しきれないケースでは、Search APIを利用することもできます。

このAPIでは、[List APIの引数に加えてmetadataの内容](https://stripe.com/docs/search#query-fields-for-prices)についても検索対象に含めることができます。

```js
const prices = await stripe.prices.search({
  query: "metadata['service']:'awesome_food_service'"
});
```

また、Search APIでは`AND`または`OR`を利用してクエリを組み立てることも可能です。
ただし、**`AND`と`OR`の併用はできません**のでご注意ください。

```js
const prices = await stripe.prices.search({
  query: "metadata['service']:'awesome_food_service' AND metadata['class']:'bronze' AND currency:'jpy'"
});
```

TypeScriptであれば、以下のようなクエリービルダー関数を用意することも可能です。

```typescript
const createPriceSearchQuery = (query : {
  [key: string]: string | {
    [metadataName: string]: string;
  };
}, operator: 'AND' | 'OR' = 'AND'): string => {  
  const queries = Object.entries(query).map(([key, value]) => {
    if (typeof value === 'string') {
      return `${key}: "${value}"`;
    }
    return Object.entries(value).map(([metadataKey, metadataValue]) => {
      return `${key}["${metadataKey}"]: "${metadataValue}"`;
    }).join(` ${operator} `);
  })

  return queries.join(` ${operator} `);
};
```

ビルダー関数を用意することで、オブジェクト形式でクエリを定義することができます。

```typescript
const prices = await stripe.prices.search({
  query: createPriceSearchQuery({
    metadata: {
      service: 'awesome_food_service',
      class: 'bronze',
    },
    currency: 'jpy',
  })
});
```

Search APIは、2022年4月に登場した新しいAPIです。

https://stripe.com/docs/search

もしmetadataを利用した検索で対応できる要件であれば、List API呼び出し後にフィルターしている処理をSearch APIに切り替えてみてもよいかもしれません。

## 方法5: Search APIで商品データを検索してから料金データを取得する

Search APIは料金以外のデータに対しても利用できます。
そのため、商品のデータに対してSearchを行うことで、取得したい料金データの種類を絞り込むことも可能です。

```js
const products = await stripe.products.search({
  query: 'active: "true" AND description~"food"'
});
const prices = await Promise.all(
  products.data.map(
    async product => {
      return stripe.prices.list({
        product: product.id,
    });
  })
);
```

ただし、取得した商品データの件数分PriceのList APIを呼び出すため、レスポンスが遅くなる可能性があることにご注意ください。

## 方法6: Webhookを利用して、別途検索インデックスを作成する

これまでの5つの方法でも難しい要件の場合、別途検索用のAPIを外部に作ることも検討できます。

その場合、Priceに関係するWebhookイベントをListenし、検索インデックスの追加・変更・削除を自動化できるようにしましょう。

```js
switch (event.type) {
  case 'price.created': {
    // 検索用DBにアイテムを追加する処理
    break;
  }
  case 'price.deleted': {
    // 検索用DBからアイテムを削除する処理
    break;
  }
  case 'price.updated': {
    // 検索用DBのアイテムを更新する処理
    break;
  }
  default:
    break;
}
```

Webhookと検索APIそれぞれのメンテナンスが必要になります。
ですが、AlgoliaやElasticsearch / Opensearchなどを利用することで、ユーザーが利用する商品検索画面にも利用できます。

## おわりに

Stripeの料金データを検索・取得する方法を６種類紹介しました。

実装のシンプルさや取得したいデータの種類、ユーザーに提供したい画面・機能に応じて、どの方法が向いていそうかの検討に役立てば幸いです。


# [PR] Stripe開発者向け情報をQiitaにて配信中！

- [Stripe Updates]：開発者向けStripeアップデート紹介・解説
- ユースケース別のStripe製品や実装サンプルの紹介
- Stripeと外部サービス・OSSとの連携方法やTipsの紹介
- 初心者向けのチュートリアル(予定)

など、Stripeを利用してオンラインビジネスを始める方法について週に２〜３本ペースで更新中です。

-> [Stripe Organizationsをフォローして最新情報をQiitaで受け取る](https://qiita.com/organizations/stripe)