Stripeで作成した「段階的な料金」または「数量ベースの料金」の詳細をAPIで取得する方法

Stripeでは、「１０ユーザーまでは固定で５,０００円、それを超えた場合は１ユーザーあたり4００円」のような、複雑な料金体系を実装することができます。

![スクリーンショット 0004-02-16 12.46.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/bffb47ac-6603-25d9-4073-d83f887b59e7.png)

また、「１５ユーザーまでは単価５０円+固定で100円、ただし１６ユーザー目以降は単価４５円と追加で固定90円で計算する」のような、より複雑なケースについてもサポートしています。

![スクリーンショット 0004-02-16 12.45.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/0fa5408e-5265-7f14-983b-6472e80b8d0f.png)

https://stripe.com/docs/products-prices/pricing-models

これらの料金体系をStripeでは、「段階的な料金」そして「数量ベースの料金」とよんでいます。

![スクリーンショット 0004-02-16 12.49.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/f6a717ef-f523-267f-374d-f3fe04c12f91.png)

# APIで料金データを取得する場合の注意点

## PricesのList APIでは「段階的な料金」または「数量ベースの料金」を取得できない

これらの複雑な料金体系では、PriceのList APIだけでデータを取得することができません。

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

[
  {
    "id": "price_xxxxx",
    "object": "price",
    "active": true,
    "billing_scheme": "tiered",
    "created": 1644979081,
    "currency": "jpy",
    "livemode": false,
    "lookup_key": null,
    "metadata": {},
    "nickname": "段階的な料金",
    "product": "prod_xxxx",
    "recurring": {
      "aggregate_usage": null,
      "interval": "month",
      "interval_count": 1,
      "trial_period_days": null,
      "usage_type": "licensed"
    },
    "tax_behavior": "inclusive",
    "tiers_mode": "graduated",
    "transform_quantity": null,
    "type": "recurring",
    "unit_amount": null,
    "unit_amount_decimal": null
  }
]
```

上記のサンプルのように、価格に関するデータが欠落した状態となります。

## 対応: PricesのRetrieve APIを利用して詳細を取得する

この対応として、Retrieve APIを利用する方法を紹介します。

```diff_javascript
  const {data: priceDatas} = await stripe.prices.list({
      product: 'prod_xxx'
  })
+  const prices = await Promise.all(priceDatas.map(async price => {
+      return stripe.prices.retrieve(price.id, {
+          expand: ['tiers']
+      })
+  }))
```

Retrieve APIの`expand`に`tiers`を指定することで、料金データの詳細を取得できます。

```diff_json
[
  {
    "id": "price_xxxxx",
    "object": "price",
    "active": true,
    "billing_scheme": "tiered",
    "created": 1644979081,
    "currency": "jpy",
    "livemode": false,
    "lookup_key": null,
    "metadata": {},
    "nickname": "段階的な料金",
    "product": "prod_xxxx",
    "recurring": {
      "aggregate_usage": null,
      "interval": "month",
      "interval_count": 1,
      "trial_period_days": null,
      "usage_type": "licensed"
    },
    "tax_behavior": "inclusive",
+    "tiers": [
+      {
+        "flat_amount": null,
+        "flat_amount_decimal": null,
+        "unit_amount": 200,
+        "unit_amount_decimal": "200",
+        "up_to": 10
+      },
+      {
+        "flat_amount": null,
+        "flat_amount_decimal": null,
+        "unit_amount": 190,
+        "unit_amount_decimal": "190",
+        "up_to": 50
+      },
+      {
+        "flat_amount": null,
+        "flat_amount_decimal": null,
+        "unit_amount": 185,
+        "unit_amount_decimal": "185",
+        "up_to": null
+      }
+    ],
    "tiers_mode": "graduated",
    "transform_quantity": null,
    "type": "recurring",
    "unit_amount": null,
    "unit_amount_decimal": null
  }
]
```

### APIのパフォーマンスには要注意


ただし、上記のサンプルでは、PriceごとにRetrieve APIを呼び出すため、APIのレスポンスが遅くなる可能性があります。
下の例では、`tiers_mode`を持たないデータではAPI呼び出しを省略する実装を追加しています。


```diff_javascript
  const prices = await Promise.all(priceDatas.map(async price => {
+      if (!price.tiers_mode) return price
      return stripe.prices.retrieve(price.id, {
          expand: ['tiers']
      })
  }))
```

もし取得が必要な料金の数が多い場合や、APIパフォーマンスを重要視したい場合は、外部のDBを用意することも検討できます。
その場合、Stripe側でWebhookを利用してデータの同期を行う運用をお勧めします。

# 終わりに

Stripeを利用することで、ユーザー数や利用数に応じた段階的・数量ベースの価格設定を行うことができます。
ただし、今回のように、その設定内容をAPIから取得し、操作するためにはすこし工夫が必要になるケースも存在します。

今後もユースケースだけでなく、より踏み込んだ実装に関するTipsもお届けしてまいります。

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