StripeとStripe Elementsで、Webhookを利用せずにサブスクリプションの支払い方法を設定する方法

Stripeで、Elementsを利用して契約時にカード情報を入力させる形でサブスクリプションを開始した場合、「次回以降の支払いに利用する決済方法」をWebhookで指定する必要がありました。

**Webhookで支払い方法を設定するサンプルコード**
引用元: https://stripe.com/docs/billing/subscriptions/build-subscriptions?ui=elements#default-payment-method

```js
if (dataObject['billing_reason'] == 'subscription_create') {
  const subscription_id = dataObject['subscription']
  const payment_intent_id = dataObject['payment_intent']

  // Retrieve the payment intent used to pay the subscription
  const payment_intent = await stripe.paymentIntents.retrieve(payment_intent_id);

  const subscription = await stripe.subscriptions.update(
    subscription_id,
    {
      default_payment_method: payment_intent.payment_method,
    },
  );
};
```

2022/06のAPIアップデートで、「Webhookを利用せずにデフォルトの支払い方法を設定する方法」が追加されましたので、紹介します。

## `save_default_payment_method`を`subscriptions.create`で設定する

Elementsを利用して決済情報を入力させるフローでは、以下のように「決済未完了のサブスクリプション」を事前に作成します。

```js
  const subscription = await stripe.subscriptions.create({
    items: [{
      price: 'price_xxx',
      quantity: 1
    }],
    customer:'cus_xxx',
    payment_behavior: 'default_incomplete',
    expand: ['latest_invoice.payment_intent'],
  })
  res.status(200).json({
    subscriptionId: subscription.id,
    clientSecret: (subscription.latest_invoice as any).payment_intent.client_secret,
  })
```

ここに新しく、`save_default_payment_method`を追加しましょう。

```diff_javascript
  const subscription = await stripe.subscriptions.create({
    items: [{
      price: 'price_xxx',
      quantity: 1
    }],
    customer:'cus_xxx',
+    payment_settings: {
+      save_default_payment_method: 'on_subscription',
+    },
    payment_behavior: 'default_incomplete',
    expand: ['latest_invoice.payment_intent'],
  })
  res.status(200).json({
    subscriptionId: subscription.id,
    clientSecret: (subscription.latest_invoice as any).payment_intent.client_secret,
  })
```

## フロントエンドの実装は変更なし

フロントエンド側での追加実装は不要です。

これまでと同じく、APIで作成したサブスクリプションのclient_secretを受け取りとElement.jsのセットアップを行います。

```jsx
import { Elements } from '@stripe/react-stripe-js'
import { loadStripe } from '@stripe/stripe-js'
import { useState } from 'react'

const Home = () => {
  const [paymentIntentClientSecret, setPaymentIntentClientSecret] = useState('')
  return (
      <main>
        <form onSubmit={async e => {
          e.preventDefault()
          const response = await fetch('/api/subscription')
          const result = await response.json()
          setPaymentIntentClientSecret(result.clientSecret)
        }}>
          <button type='submit'>購読する</button>
        </form>
        <section>
          {paymentIntentClientSecret ? (
          <Elements
            options={{
              loader: 'always',
              appearance: {
                theme: 'stripe'
              },
              clientSecret: paymentIntentClientSecret,
            }}
            stripe={loadStripe('pk_test_xxx')}
          >
            <SubscriptionForm />
          </Elements>

          ): null}
        </section>
      </main>
  )
}
```

あとは`useStripe`などを利用して、決済処理を行うだけです。

```jsx
import { PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js'

const SubscriptionForm = () => {
  const stripe = useStripe()
  const elements = useElements()
  return (
    <form onSubmit={async e => {
      e.preventDefault()
      if (!elements || !stripe) return;
      const { error } = await stripe.confirmPayment({
        elements,
        confirmParams: {
          return_url: 'http://localhost:3000'
        }
      })
      console.log(error)
    }}>
      <PaymentElement />
      <button type='submit'>定期購読を開始する</button>
    </form>
  )
}
```

## Webhookを利用する必要があるケース

今回のAPIアップデートに対応することで、Webhookでデフォルトの決済方法を指定する必要はなくなります。

ですが、今後も「初回の決済に成功した場合に、システム側で処理を行う」要件が発生した場合には、Webhookを利用する必要があります。

- 銀行振込など、その場で決済成功を確認できない決済手段をサポートするケース
- 支払いの完了をもってサービスの提供を開始させたいケース
- CRM / MAなど、外部ツールやDBとデータを連携させたいケース
- etc..

これらのケースでは、以下のWebhookイベントと条件を利用したAPIの実装が引き続き必要です。

```js
if (event.type !== 'customer.subscription.updated') return
const dataObject = event.data.object
if (dataObject['status'] == 'active') {
  // 各種処理を記述
}
```


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

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

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

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