Stripeを使って、クレジットカードとPaidy両方に対応した決済フォームを簡単に実装する方法

複数の決済手段をサポートする場合、決済フォームやフローの組み込みは数に比例して難しくなります。決済手段を選択するUIの構築やロゴ・ボタンの見た目やサイズの調整、選択された決済手段ごとに支払い処理を実行するコードを実装するなど、考慮事項や開発タスクの数は決して少なくありません。そのため「売り上げを増やすために、決済手段を増やす」判断をする場合には、開発スケジュールや保守性といった社内のリソースの捻出が課題になりがちです。

この記事では、決済手段を選択するUIの組み込みを効率化し、決済手段を追加する手間を減らす方法の１つとして、Stripe ElementsとPaidyを利用した「クレジットカード決済と後払い」の両方をサポートする方法を紹介します。

## Stripe Elementsで、クレジットカードとPaidyを選択できるUIを簡単に実装する

Paidyのような、「いま（2024/8）の時点でStripe単独では処理できない決済手段」についても、Stripe Elementsで作成した決済フォームに組み込むことができます。組み込み方はとても簡単で、**ブラウザ側のJavaScriptにて、`externalPaymentMethodTypes`に利用したい決済手段を設定するだけ**です。

```diff_html
<script src="https://js.stripe.com/v3/"></script>
<form id="payemnt-form">
  <div id="payment-element"></div>
</form>
<script>
const stripe = Stripe('${STRIPE_PUB_API_KEY}');

// Stripe Elementsを初期化
const elements = stripe.elements({
  mode: 'payment',
  amount: 1000,
  currency: 'jpy',
+  externalPaymentMethodTypes: ['external_paidy']
});
const paymentElement = elements.create('payment', {
  layout: 'accordion',
});
paymentElement.mount('#payment-element');
</script>
```

この値を追加すると、フォームの選択肢がクレジットカードとPaidyの２択になります。

![スクリーンショット 2024-08-05 14.10.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/61c84587-aa2e-380f-c619-4d2eab28181f.png)

追加で連携する決済手段は、配列で指定できます。そのため、次のサンプルコードのように、特定の条件でのみ利用できるようにも作れます。

```diff_javascript
+      const externalPaymentMethodTypes = []
+      if (/* Paidyを利用できる条件をここで定義する */) {
+        externalPaymentMethodTypes.push('external_paidy')
+      }
      const elements = stripe.elements({
        mode: 'payment',
        amount: 1000,
        currency: 'jpy',
+        externalPaymentMethodTypes
      });
```

## Paidyとクレジットカードそれぞれの決済フローを実装しよう

Stripe Elementsを使うと、ブラウザ側のJavaScriptのみで、簡単に決済フォームに複数種類の決済手段を提示できます。続いて`externalPaymentMethodTypes`で追加した決済手段での決済処理を実装します。

追加された決済手段では、次の画像のように「決済手段のサービスへリダイレクトする」挙動を実装します。この例では、PaidyのSDKやAPIキーを利用して、Paidy Checkoutで決済を進めます。

![スクリーンショット 2024-08-05 14.10.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/b4d2bc78-92d5-2684-4adc-e0e7fffe5927.png)

まずはPaidyのSDKを読み込みましょう。Paidy Checkoutでの決済処理は、このSDKを利用して実装します。

```diff_html
<script src="https://js.stripe.com/v3/"></script>
+<script type="text/javascript" src="https://apps.paidy.com/"></script>
<form id="payemnt-form">
  <div id="payment-element"></div>
</form>
```

:::note warn
Paidy Checkoutを組み込むには、Paidyの公開可能APIキーが必要です。APIキーの種類や取得方法については、[Paidyのドキュメント](https://paidy.com/docs/api/jp/#1-)をご覧ください。
:::

続いて、顧客がどの決済手段を選択したかを判別できるようにしましょう。Stripe Elementsで作成したPayment Elementのイベントを利用して、いまどの決済手段が選ばれているかを更新します。

```javascript
let selectedPaymentMethod;

paymentElement.on('change', event => {
  selectedPaymentMethod = event.value.type
})
```

続いて決済フォームを送信した際の動作を作ります。まずはStripeを利用したクレジットカード決済処理を作ります。

```javascript
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const result = await stripe.confirmPayment({
    elements,
    confirmParams: {
      return_url: 'http://localhost:54072',
    },
  });

  console.log(result)
  if (result.error) {
    const errorMessage = document.getElementById('error-message');
    errorMessage.textContent = result.error.code + ':' + result.error.message;
  }
});
```

続いてPaidyの決済フローを追加しましょう。**`selectedPaymentMethod`が`external_paidy`の場合**、Paidy Checkoutの処理を実行します。注文に関する情報だけでなく、Paidyアカウントの作成日などの販売者情報についても送信する必要がある点にご注意ください。

```diff_javascript

// フォームの送信イベントを処理
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
  event.preventDefault();
+  if (selectedPaymentMethod === 'external_paidy') {
+      const config = {
+           "api_key": "${Paidyの公開可能APIキーをここに設定する}",
+           "logo_url": "http://www.paidy.com/images/logo.png",
+           "closed": function(callbackData) {
              // {"status":"closed"}
              // {"amount":10000,"created_at":"2024-07-12T03:02:59.143Z","currency":"JPY","id":"pay_ZpCc4zAAAFYAC6FW","status":"authorized"}
+              console.log(JSON.stringify(callbackData))
+           }
+      };
+      const paidyHandler = Paidy.configure(config); 
+      const payload = {
+           "amount": 1000,
+           "currency" : "JPY",
+           "buyer": {
+               "name1": "山田　太郎",
+           },
+           "buyer_data": {
+               "account_registration_date": "2024/04/01",
+               "ltv": 0,
+               "order_count": 1,
+               "last_order_amount": 0,
+               "last_order_at": 0,
+           },
+           "order": {
+               "items": [{
+                   "quantity":1,
+                   "unit_price":100,
+               }],
+           },
+           "shipping_address": {
+               "state": "東京都",
+               "zip": "106-2004"
+           },
+      };
+      paidyHandler.launch(payload);
+      return;
+  }
  // Use the clientSecret and Elements instance to confirm the setup
  const result = await stripe.confirmPayment({
    elements,
    confirmParams: {
      return_url: 'http://localhost:54072',
    },
  });

  console.log(result)
  if (result.error) {
    const errorMessage = document.getElementById('error-message');
    errorMessage.textContent = result.error.code + ':' + result.error.message;
  }
});
```

`paidyHandler.launch`を実行すると、Paidy Checkoutのモーダル画面が表示されます。テスト用のメールアドレスや電話番号、そして認証コードについてはPaidyの販売・開発者向けダッシュボードに記載されていますので、その情報をご利用ください。

![スクリーンショット 2024-08-05 14.10.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/be41e801-5806-0ba7-5198-f52571f84184.png)

Paidyでの決済処理が終了すると、成功・失敗どちらの場合でも、`Paidy.configure`の引数内にある`closed`で定義した処理が実行されます。`status`が`closed`の場合は決済が完了しておらず、`authorized`の場合は認証が完了しています。そのため、この値を元に注文完了画面へのリダイレクト処理を実行しましょう。

## 注文結果をデータベースに保存する方法

Paidyでの決済結果はPaidyに、Stripeでの決済結果はStripeに保存されます。そのため、すべての決済情報を確認するには、それぞれのデータをまとめるデータベースが必要です。Stripe / PaidyどちらもWebhookイベントをサポートしていますので、Webhook APIをStripeとPaidyそれぞれ向けに用意しましょう。

### StripeのWebhookイベントから、注文テーブルにデータを送信するコードサンプル

StripeのWebhookでは、まずStripeからのAPIリクエスト以外を受け付けないようにする処理を実装します。その後、決済が完了したことを示すイベント`payment_intent.succeeded`の時に注文情報を保存しましょう。

```js
app.post('/webhook', async (context) => {
  const { STRIPE_SECRET_API_KEY, STRIPE_WEBHOOK_SECRET } =
    env(context)
  const stripe = new Stripe(STRIPE_SECRET_API_KEY)
  const signature = context.req.header('stripe-signature')
  try {
    if (!signature) {
      return context.text('', 400)
    }
    const body = await context.req.text()
    const event = await stripe.webhooks.constructEventAsync(
      body,
      signature,
      STRIPE_WEBHOOK_SECRET
    )
    switch (event.type) {
      case 'payment_intent.succeeded': {
        const payment = event.data.object
        await putOrder({
          key: payment.id,
          created: payment.created_at,
          type: 'stripe',
          status: payment.status
        })
        break
      }
      default:
        break
    }
    return context.text('', 200)
  } catch (err) {
    const errorMessage = `⚠️  Webhook signature verification failed. ${
      err instanceof Error ? err.message : 'Internal server error'
    }`
    console.log(errorMessage)
    return context.text(errorMessage, 400)
  }
})
```

### PaidyのWebhookイベントから、注文テーブルにデータを送信するコードサンプル

[Hono](https://hono.dev)を使って簡単なAPIを実装しました。Paidyからのリクエストであることを検証するため、PaidyのAPIを利用して支払いの詳細を取得しています。Paidy APIを利用するには、PaidyのシークレットAPIキーが必要ですので、環境変数などで設定しましょう。

```js
app.post('/webhook', async c => {
  const { PAIDY_SEC_API_KEY } = env(c)
  const { payment_id } = await c.req.json()
  const payment = await fetch(`https://api.paidy.com/payments/${payment_id}`, {
    headers: {
      'Content-Type': 'application/json',
      'Paidy-Version': '2018-04-10',
      'Authorization': `Bearer ${PAIDY_SEC_API_KEY}`
    }
  }).then(data => data.json())
  await putOrder({
    key: payment_id,
    created_at: Math.floor(new Date(payment.created_at).getTime() / 1000),
    type: 'paidy',
    status: payment.status
  })
})

```

### StripeとPaidyで`create`/`created_at`の書式が違う点に注意しよう

決済サービスによって、日付や金額の持ち方が異なります。例えばStripeの`create`はUNIXタイムスタンプで取得できますが、Paidyにおける`created_at`は文字列として送信されます。そのためDBに保存する際は、これらのデータタイプの違いを把握して、データベースで指定している型に変換する処理を実装しましょう。

## Stripeを使って「開発生産性の向上」を始めよう

このようにStripeを使うことで、クレジットカード決済以外の決済手段をより簡単に組み込むことができます。今回は直接APIを呼び出してリダイレクトさせる形で決済を処理する方法を紹介しました。コンビニ決済や銀行振込のように、日本独自の決済手段をStripeがサポートするようになった場合にも、Stripe側の処理に寄せることで簡単に移行することが可能です。

## 関連記事・ドキュメント

### Paidyについて

https://paidy.com/docs/jp/webhook.html

https://paidy.com/docs/jp/paidycheckout.html#howitworks


### Stripe Elementsで複数の決済手段をサポートする方法


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

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