SvelteKitとsvelte-stripeで、サブスクリプション申込フローを実装する方法

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

SveltKitはSvelteでアプリケーションを構築するためのフレームワークです。

https://kit.svelte.jp/


2022/11時点では、まだVersion1.0.0がリリースされていませんが、Svelteを使う上では注目しておきたいフレームワークではないかと思います。

この記事では、`svelte-stripe`ライブラリを利用して、SveltKitでサブスクリプション申込のUI / APIを実装する方法を紹介します。

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

セットアップは、以下のコマンドで開始します。

```bash
$ npm create svelte@latest sveltekit-app
```
テンプレートを利用するかどうかや、TypeScript, ESLint, Prettier, Playwriteを利用するかを聞かれます。

```
? Which Svelte app template? › - Use arrow-keys. Return to submit.
❯   SvelteKit demo app - A demo app showcasing some of the features of SvelteKit - play a word guessing game that works without JavaScript!
    Skeleton project
    Library skeleton project
```

今回は、以下の設定でセットアップしました。

```bash
✔ Which Svelte app template? › SvelteKit demo app
✔ Add type checking with TypeScript? › Yes, using TypeScript syntax
✔ Add ESLint for code linting? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Playwright for browser testing? … No / Yes
```

`Next steps`が表示されれば、セットアップ完了です。

```bash
Next steps:
  1: cd sveltekit-ap
  2: npm install (or pnpm install, etc)
  3: git init && git add -A && git commit -m "Initial commit" (optional)
  4: npm run dev -- --open

To close the dev server, hit Ctrl-C

Stuck? Visit us at https://svelte.dev/chat
```

指示に従ってコマンドを実行しましょう。

```bash
$ cd sveltekit-app
$ npm install (or pnpm install, etc)
$ git init && git add -A && git commit -m "Initial commit"
$ npm run dev -- --open
```

セットアップに成功していれば、デモアプリが立ち上がります。

![スクリーンショット 2022-11-17 17.53.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/c1afcda9-85ca-237c-448d-a5cd1f776dd6.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_...
```

https://kit.svelte.jp/docs/server-only-modules#private-environment-variables

### ページ読み込み時に、Subscriptionデータを生成する

SvelteKitがページを読み込む際に、サーバー側でサブスクリプションデータを生成させます。

`src/routes/subscription/+page.server.ts`ファイルを作成し、以下のコードを追加しましょう。

```ts
import { STRIPE_SECRET_KEY } from '$env/static/private';
import Stripe from 'stripe';
 
/** @type {import('./$types').PageServerLoad} */
export async function load() {
    const stripe = new Stripe(STRIPE_SECRET_KEY, {
        apiVersion: '2022-11-15'
    });
    const customer = await stripe.customers.create()
    const product = await stripe.products.create({
        name: 'SvelteKitデモ用'
    })
    const subscription = await stripe.subscriptions.create({
        customer: customer.id,
        items: [{
            price_data: {
                unit_amount: 1000,
                currency: 'jpy',
                recurring: {
                    interval: 'month',
                },
                product: product.id
            }
        }],
        payment_behavior: 'default_incomplete',
        expand: ['latest_invoice.payment_intent'],
        payment_settings: {
            save_default_payment_method: 'on_subscription',
        },
    })

    return {
        subscriptionId: subscription.id,
        clientSecret: ((subscription.latest_invoice as Stripe.Invoice).payment_intent as Stripe.PaymentIntent).client_secret
    }
}
```

#### Subscription作成処理の参考

今回のサンプルコードは、事前にダッシュボードでデータを作成する必要のないように用意しました。

事前準備が不要な代わりに、ページ読み込みの度にCustomerや商品データが生成されますのでご注意ください。

引数の解説等については、以下の記事で紹介しています。

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

https://qiita.com/hideokamoto/items/6bd31422e2ebb27d4eaa

### 生成したSubscriptionデータを元に、決済フォームを表示する

続いて決済フォームを表示させましょう。

`src/routes/subscription/+page.svelte`を作成します。

```html
<script lan="ts">
  /** @type {import('./$types').PageData} */
  import { loadStripe } from '@stripe/stripe-js'
  import { Elements, PaymentElement } from  'svelte-stripe'
  import { onMount } from 'svelte';
  import { PUBLIC_STRIPE_PUBLIC_KEY } from '$env/static/public';

    export let data
    export const { clientSecret } = data
    let stripe = null
    let elements

    onMount(async () => {
        stripe = await loadStripe(PUBLIC_STRIPE_PUBLIC_KEY)
    })
</script>

<div>
    <h1>hello</h1>
    {#if stripe && clientSecret}
        <form>
            <Elements {stripe} {clientSecret} bind:elements>
                <PaymentElement />
                <button type='submit'>Subscribe</button>
            </Elements>
        </form>
    {/if}
</div>
```

http://localhost:5173/subscription にアクセスして、決済フォームが表示されれば成功です。

![スクリーンショット 2022-11-18 16.17.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/b5f020a8-ce94-de53-7dd0-20ac8bd224f5.png)

実際の組み込みでは、契約内容（料金・期間など）を表示する必要がある点にご注意ください。

## サブスクリプションの申込処理を完了する

あとはformのsubmit処理を追加して、申し込みを完了させるだけです。

先ほど作成した`+page.svelte`を編集して、submit処理を追加しましょう。

```diff_html
<script lan="ts">
  /** @type {import('./$types').PageData} */
  import { loadStripe } from '@stripe/stripe-js'
  import { Elements, PaymentElement } from  'svelte-stripe'
  import { onMount } from 'svelte';
  import { PUBLIC_STRIPE_PUBLIC_KEY } from '$env/static/public';

    export let data
    export const { clientSecret } = data
    let stripe = null
    let elements

    onMount(async () => {
        stripe = await loadStripe(PUBLIC_STRIPE_PUBLIC_KEY)
    })
+    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')
+        }
+    }
</script>
```

続いてformタグを編集し、submitイベントを設定します。

```diff_html
-        <form>
+        <form on:submit|preventDefault={submit}>
```

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)

## Stripeの料金表を使って、手早く組み込む方法

ここまで決済フォームを埋め込む方法を紹介しましたが、Stripeでは料金表そのものを埋め込むことも可能です。

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

ダッシュボードで生成した料金表の、コードスニペットを埋め込むだけで実装できますので、手軽に実装できます。

```html

<div class="text-column">
	<h1>料金表</h1>
</div>

<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
<stripe-pricing-table
	pricing-table-id="prctbl_xxxx"
	publishable-key="pk_test_xxxxx">
</stripe-pricing-table>
```

![スクリーンショット 2022-11-18 16.26.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/dad72690-97ad-c0c1-ea73-635483bde782.png)