svelte-stripeを利用してSvelteにStripeの決済フォームを実装する

この記事は、[Svelte Advent Calendar 2022](https://qiita.com/advent-calendar/2022/svelte) 13日目の記事です。


Svelteは、WebアプリケーションやGUIを構築するためのUIライブラリの１つです。

https://svelte.jp/

Reactなどと異なり、仮想DOMを利用せず、軽量かつシンプルに実装できることを謳っています。

Svelteでアプリケーションを開発する場合、`svelte-stripe`ライブラリを利用することで、Stripeでのオンライン決済機能の組み込みが簡単に行えます。

https://www.sveltestripe.com/

## Viteでプロジェクトをセットアップする

早速Svelteアプリを作成しましょう。

今回はViteを利用します。

```bash
$ yarn create vite svelte-app --template svelte-ts

..

Scaffolding project in /Users/sandbox/svelte-app...

Done. Now run:

  cd svelte-app
  yarn
  yarn dev

✨  Done in 3.34s.
```

完了メッセージに表示されたコマンドを実行して、セットアップされたアプリを起動しましょう。

```bash
$ cd svelte-app
$ yarn
$ yarn dev
```

![スクリーンショット 2022-11-15 17.46.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/1aaf9eba-2eb5-d5cc-6f6a-071d06b6b562.png)

## svelte-stripeでStripeの決済フォームを組み込む

セットアップが完了しましたので、Stripeの組み込みを行いましょう。

今回はコミュニティライブラリのsvelte-stripeを利用します。

https://www.sveltestripe.com/

### ライブラリのインストール

まずはライブラリをインストールしましょう。

```bash
$ yarn add stripe svelte-stripe
```

### 環境変数の設定

StripeのAPIキーを`.env.local`ファイルに保存しましょう。

```
VITE_STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
```

### Stripe用のコンポーネントファイルを作成

Stripeに関するリソースをまとめるファイル・コンポーネントを用意しましょう。

今回は、`src/lib/Stripe.svelte`ファイルを作成しました。

```html
<script>
  import { loadStripe } from '@stripe/stripe-js'
  import { Elements } from  'svelte-stripe'
  import { onMount } from 'svelte';

  let stripe = null

  onMount(async () => {
    stripe = await loadStripe(import.meta.env.VITE_STRIPE_PUBLIC_KEY)
  })
</script>

{#if stripe}
  <Elements {stripe}>
    <!-- this is where your Stripe components go -->
    <b>Hello Stripe</b>
  </Elements>
{/if}
```

これを`src/App.svelte`で読み込みます。

```diff_html
<script lang="ts">
  import svelteLogo from './assets/svelte.svg'
  import Counter from './lib/Counter.svelte'
+  import StripeFrom from './lib/Stripe.svelte'
</script>

...

  <div class="card">
    <Counter />
+    <StripeFrom />
  </div>
```

カウンターの横に、「Hello Stripe」が表示されていれば、追加成功です。

![スクリーンショット 2022-11-15 21.09.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/2a3dd84e-7006-70fe-5b3f-11df62d527b6.png)

## Stripeの決済フォームを描画する

Stripe組み込みの準備ができましたので、決済フォームを表示させましょう。

Svelteにはサーバー側の機能がないため、Stripeのクイックスタートなどで別途Payment Intentを作成するAPIを用意します。

https://stripe.com/docs/payments/quickstart

続いてコンポーネント内のコードを変更します。

```diff_html
<script>
  import { loadStripe } from '@stripe/stripe-js'
-  import { Elements } from  'svelte-stripe'
+  import { Elements, PaymentElement } from  'svelte-stripe'
  import { onMount } from 'svelte';

  let stripe = null
+  let clientSecret = ''
+  let elements

  onMount(async () => {
    stripe = await loadStripe(import.meta.env.VITE_STRIPE_PUBLIC_KEY)
+    const response = await fetch('http://localhost:3100/stripe', {
+        method: 'POST'
+    })
+    const result = await response.json()
+    clientSecret = result.client_secret // クイックスタートの場合、「result.clientSecret」
  })
</script>

-{#if stripe}
+{#if stripe && clientSecret}
-  <Elements {stripe}>
+  <Elements {stripe} {clientSecret} bind:elements>
+    <PaymentElement />
  </Elements>
{/if}
```

まず、`onMount`にてPayment Intentを作成するAPIを呼び出し、ClientSecretを取得しています。

次に、取得したClientSecretを利用して、PaymentElementを表示させています。

最後に、Elementsを`elements`変数にbindして、この後のsubmitイベントに備えています。

変更を保存すると、決済フォームが画面に表示されます。

![スクリーンショット 2022-11-15 21.21.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/faf7b6ae-1dda-f3aa-5663-4eb867115bfa.png)

## 決済フォームのSubmit処理を実装する

最後に、決済を完了するためのイベントを実装します。

以下の関数を`Stripe.svelte`に追加しましょう。

```js
  async function submit() {
    if (!stripe || !elements) {
        throw new Error('stripe or elements not defined')
    }
    const { error, paymentIntent } = await stripe.confirmPayment({
        elements,
        redirect: 'if_required'
    })
    if (error) {
        window.alert(JSON.stringify(error))
    } else {
        window.alert('done')
    }
  }
```

続いて、`Stripe.svelte`のHTMLを以下のように変更します。

```diff_html
-  <Elements {stripe} {clientSecret} bind:elements>
-    <PaymentElement />
-  </Elements>
+    <form on:submit|preventDefault={submit}>
+      <Elements {stripe} {clientSecret} bind:elements>
+        <PaymentElement />
+        <button type='submit'>Pay now</button>
+      </Elements>
```

`form`タグを追加し、`on:submit`でフォームのsubmitイベントを設定しました。

これでフォームにボタンが追加され、決済を完了できるようになります。

![スクリーンショット 2022-11-15 21.30.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/77bdac21-5d7d-d8ff-47aa-cfe2da89bc3a.png)

Stripeが用意している「テストカード番号」を利用して、決済をテストしましょう。

https://stripe.com/docs/testing

成功メッセージが表示されれば、実装完了です。

![スクリーンショット 2022-11-15 21.29.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/b7fc1e8c-3496-244c-2e22-c85271732226.png)

## 追加の学習リソース

PaymentElementを利用しない場合の、クレジットカード以外の実装方法などについては、公式のサンプルを参考にできます。

https://www.sveltestripe.com/#examples

コンビニ決済やApple Pay/Google Payなどもサポートされています。

SvelteKitを利用して、Payment IntentやSetup Intentを作成する方法やWebhook APIを実装する方法なども紹介されています。

https://www.sveltestripe.com/#creating-a-payment-intent