Next.js + Stripeでめちゃくちゃ簡単に月額課金を実装する方法

決済まわりはお金が直接絡んでいて、けっこう面倒な印象が強いですが、Stripeを利用したらあっという間に決済処理を実装することができます。

諸々自前でカスタマイズする方法もありますが、今回はStripe側で用意されている決済ページなどを利用して、簡単に実装する方法を備忘録ついでにまとめます(カスタマイズする話が多いですからね)

1) まずはStraipeに登録&ログインして、「テスト連携」を有効化しましょう

2) Stripeのファーストビューに公開可能キーとシークレットキーがあるので、その2つをNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=xxxxxxxとSTRIPE_SECRET_KEY=xxxxxxxという感じで、環境変数に設定しましょう

3) 次は「商品カタログ」から月額課金する商品情報を新規作成します

4) 月額課金用の商品を登録したら、作成した商品のAPI IDという項目をコピーしましょう

5) Next.jsのpages/api/checkout.tsを作成します

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

export default async function handler(req: any, res: any) {
    if (req.method === 'POST') {
        const email = req.body?.email;
        const userId = req.body?.userId;

        if (!email || !userId) {
            res.status(403).end('email or userId is required');
            return;
        }

        try {
            // 顧客を作成または既存の顧客を検索
            const customer = await stripe.customers.list({
                email,
            }).then((customers: any) => {
                if (customers.data.length) {
                    return customers.data[0];
                }
                return stripe.customers.create({
                    email,
                    metadata: {
                        userId: userId,
                    },
                });
            });

            const session = await stripe.checkout.sessions.create({
                customer: customer.id, // 顧客IDをセッションに紐付け
                payment_method_types: ['card'],
                line_items: [
                    {
                        price: "[コピーした月額商品のAPI ID]",
                        quantity: 1,
                    },
                ],
                billing_address_collection: 'required',
                mode: 'subscription',
                success_url: `${req.headers.origin}/?success=true`,
                cancel_url: `${req.headers.origin}/?canceled=true`,
            });
            res.redirect(303, session.url);
        } catch (err: any) {
            res.status(500).json(err.message);
        }
    } else {
        res.setHeader('Allow', 'POST');
        res.status(405).end('Method Not Allowed');
    }
}

このcheckout.tsは、

フロントエンドのformから/api/checkoutへアクセス

POSTでメールアドレスをユーザーIDを受け取る

Stripeにすでに顧客情報があるかメールアドレスで検索し、
顧客情報が存在しない場合は、顧客情報を新規作成
(ちなみに顧客情報の識別子はemailでないといけない仕様らしいです)

作成した顧客情報の顧客IDを紐付けてStripe側のセッションを作成し、
Stripe側の決済ページ(月額課金の登録ページ)へ飛ぶ

というフローをしています!

後は、フロントエンド側で、

import React, { useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";

const key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripePromise = loadStripe(key);

const App = () => {
    return (
        <form action="/api/checkout" method="POST">
            <section>
                <input type="hidden" name="email" value="user-example@gmail.com" />
                <input type="hidden" name="userId" value="123" />
                <button type="submit" role="link">
                    月額プランの登録ページへ移動する
                </button>
            </section>
        </form>
    );
};

export default App;

という感じでformを用意してあげたら、

という感じで、
Stripe側の月額登録ページへ移動することができます!
(ちなみに4242 4242 4242 4242というテストのカード番号を使うことができます。有効期限とセキュリティコードは適当でOKです)

6) 次は、Next.jsのpages/api/portal.tsというファイルを作成して、同じような手順でStripe側の「カード情報変更や決済履歴表示ページ」へ移動します
下記のコードをpages/api/portal.tsに書きましょう

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

export default async function handler(req: any, res: any) {
    if (req.method === 'POST') {
        const email = req.body?.email;

        if (!email) {
            res.status(403).end('email is required');
            return;
        }

        // 顧客を検索
        const customers = await stripe.customers.list({
            email,
        });
        let customer;
        if (customers.data.length) {
            customer = customers.data[0];
        } else {
            res.status(403).end('customer not found');
            return;
        }

        const session = await stripe.billingPortal.sessions.create({
            customer: customer.id,
            return_url: `${req.headers.origin}/?return=true`,
        });

        res.redirect(303, session.url);
    }
}

このportal.tsは、

emailをもとにStripe似登録している顧客情報を検索

取得した顧客情報の顧客IDをもとに、Stripe側のページへ移動しています

後は、フロントエンド側で、

import React, { useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";

const key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripePromise = loadStripe(key);

const App = () => {
    return (
        <form action="/api/portal" method="POST">
            <section>
                <input type="hidden" name="email" value="user-example@gmail.com" />
                <input type="hidden" name="userId" value="123" />
                <button type="submit" role="link">
                    Stripeのユーザー画面へ
                </button>
            </section>
        </form>
    );
};

export default App;

という感じで書いて、
「Stripeのユーザー画面へ」というボタンを押したら、

↑のようなStripe側のユーザーページへ移動することができます!

たったこれだけで、
月額課金の登録、プラン変更、カード変更、決済の履歴などほぼすべての管理画面の出来上がりです!

後はユーザーさんのカードで決済処理に失敗した時などの対応ですが、

決済に失敗した時は↑のようなwebhookで通知を受けることができるので、
こちらでwebhook用のAPIを作ってあげて、決済に失敗したよという通知を受け取るだけで、
後は自由に処理を実装する感じでOKです!

たったこれだけで、月額課金の基本処理がすべて揃ってしまいます!
カスタマイズする記事は多いですが、シンプルな用途ならこれだけで十分に実用的だと思いますし、ユーザーページなどはOpenAIもそのまま使っているので、全然いけると思います!

Strapiで月額課金の実装を楽にしましょう!