Webhookを使って、Stripe Payment Linksで作成した支払いリンクを「先着１名限定」にする

StripeのPayment Linksを利用して、簡単に支払いリンクを作ることができます。

そしてWebhookとPayment Links APIを利用することで、より柔軟に決済リンクを運用することが可能です。

この記事では、Webhookを利用したユースケースのアイディアとして、「先着１名さま限定」の支払いリンクを作る方法を紹介します。

## 実装するもの

- 支払いリンクを利用した決済の完了を受け取るWebhookを実装する
- 支払いが完了したことを判定して、支払いリンクを無効化する
- 決済が失敗した場合は、再度有効化する

### 考慮が必要なポイント

コンビニ決済など、注文後に顧客が別途入金・支払いを行う必要がある決済もStripeではサポートしています。
これらの決済方法では、「期日経過」によって決済が失敗する可能性があります。
そのため、「注文時に無効化するが、決済に失敗した場合は再度有効化する」仕組みを実装する必要があります。

## Node.js(Express)でデモのWebhookを実装する

ここからは、Expressを利用して、Webhook　APIの実装を紹介します。

```js
app.post('/webhook/payment_links', (request, response => {
  const dataObject = event.data.object;

  // ここに実装を追加します。

  return response.status(204).end();
});
```

### `event.type`で無関係なイベントを処理しないように判定する

まず、関係のないイベントを除外しましょう。

```diff_javascript
app.post('/webhook/payment_links', (request, response => {
  const dataObject = event.data.object;
+  /**
+   * Checkout /　支払いリンクでの決済と関係のないイベントを除外
+   */
+  if (!['checkout.session.completed', 'checkout.session.async_payment_succeeded', 'payment_intent.payment_failed'].includes(event.type)) {
+    return response.status(204).end();
+  }
  return response.status(204).end();
});
```

### 支払いリンクを無効化する
続いて支払いリンクを無効化する処理を追加しましょう。


```diff_javascript
  /**
   * Checkout /　支払いリンクでの決済と関係のないイベントを除外
   */
  if (!['checkout.session.completed', 'checkout.session.async_payment_succeeded', 'payment_intent.payment_failed'].includes(event.type)) {
    return response.status(204).end();
  }

+  /**
+   * Checkoutのセッションが完了しているならば、
+   * 他の注文が来ないように支払いリンクを無効化する
+   */
+  if (['checkout.session.completed', 'checkout.session.async_payment_succeeded'].includes(event.type)) {
+    await stripe.paymentLinks.update(dataObject.payment_link, {
+      active: false,
+    });
+  }

  return response.status(204).end();
});
```

### コンビニ決済などの決済をサポートする

ここからは、コンビニ決済などの即時決済ではない決済方法をサポートする実装を追加します。
支払い期限をオーバーしたことによる決済失敗を処理するために、支払いリンクのIDをPayment Intentのmetadataに保存します。

```diff_javascript
  /**
   * Checkoutのセッションが完了しているならば、
   * 他の注文が来ないように支払いリンクを無効化する
   */
  if (['checkout.session.completed', 'checkout.session.async_payment_succeeded'].includes(event.type)) {
    await stripe.paymentLinks.update(dataObject.payment_link, {
      active: false,
    });
  }

+  /**
+   * コンビニ決済など、決済まで完了していないケース
+   * 決済失敗時に再有効化させたいのでmetadataに持たせる
+   */
+  if (event.type === 'checkout.session.completed' && dataObject.payment_status !== "paid") {
+    await stripe.paymentIntents.update(dataObject.payment_intent, {
+      metadata: {
+        payment_links_id: dataObject.payment_link,
+      },
+    })
+  }
  return response.status(204).end();
});
```

そして最後に、決済が失敗した場合は支払いリンクを再度有効化する処理を追加します。


```diff_javascript
  /**
   * コンビニ決済など、決済まで完了していないケース
   * 決済失敗時に再有効化させたいのでmetadataに持たせる
   */
  if (event.type === 'checkout.session.completed' && dataObject.payment_status !== "paid") {
    await stripe.paymentIntents.update(dataObject.payment_intent, {
      metadata: {
        payment_links_id: dataObject.payment_link,
      },
    });
-  }
+  } else if (event.type === 'payment_intent.payment_failed') {
+    const paymentLinksId = dataObject.metadata.payment_links_id;
+    if (paymentLinksId) {
+      await stripe.paymentLinks.update(paymentLinksId, {
+        active: true,
+      });
+    }
+  }
  return response.status(204).end();
});

```

## 実際に導入を検討する際の注意点

### 1: このサンプルは、すべての支払いリンクを一時停止します
このサンプルでは、**「すべての支払いリンク」が先着１名限定**になります。
そのため、一部のリンクだけ先着限定にしたいケースでは、無効化すべきリンクかどうかを判定する実装が必要です。

支払いリンクのmetadataを利用する方法や、別途DBを用意する方法などが考えられますので、ぜひお試しください。

### 2: 同時アクセスへの考慮が必要

今回のデモは、「支払いリンク経由での決済が完了してから、実行されるプログラム」です。
そのため、同時に支払いリンクへアクセスがあった場合などでは、複数の注文が入る可能性があります。

より厳密な「先着１名」を実装したい場合は、支払いリンクの代わりにCheckoutを利用することをお勧めします。
Checkoutであれば、セッション作成処理を事前にサーバー側で実装するため、その時点でDBを利用してロック・ロック解除などの処理を追加することができます。

## おわりに

今回は、Stripeの支払いリンクをWebhookやAPIを利用してより広いユースケースに用いるアイディアを紹介しました。

Webhook / APIを活用した、幅広いユースケースのアイディアがある方は、ぜひご自身のブログやQiita・またはこの記事へのコメントなどでお聞かせください。


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

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

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

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