💳

Stripe Checkout を用いて利用規約同意を実装する

2024/11/15に公開

Stripe Checkout を使用してユーザーの支払いフローに利用規約への同意を組み込む方法を解説します。このブログでは、実装の流れや技術的なポイントを解説しつつ、具体的な TypeScript コード例を交えて解説します。

Stripe Checkout とは

Stripe Checkout は、Stripe が提供する事前構築済みの支払いフローです。数行のコードで以下を実現できます。

安全なクレジットカード処理

Apple Pay や Google Pay などのデジタルウォレット対応
グローバルな支払い通貨対応
ユーザーインターフェースの国際化
Stripe Checkout のもう一つの強みは、支払いフローのカスタマイズが可能なことです。これにより、独自のカスタムフィールドを追加し、ユーザーに特定の情報を提供したり取得することができます。

なぜ利用規約同意が必要なのか

SaaS アプリケーションやサブスクリプションベースのサービスを運営する場合、利用規約同意は法律的な保護の観点で重要です。これにより、次の点を確認できます。

  • ユーザーがサービスの利用条件を理解している。
  • ユーザーが料金の支払い義務を認識している。
  • サービスの不正利用を防ぐ。

Stripe Checkout の「カスタムフィールド」機能を活用すれば、利用規約同意のフローをスムーズに組み込むことができます。

利用規約同意の設計概要

以下のステップで利用規約同意を支払いフローに統合します。

  1. Checkout セッション作成時にカスタムフィールドを追加

    • 「利用規約に同意しますか?」というチェックボックスを Checkout に追加します。
  2. ユーザーが同意するまで支払いを進められないようにする

    • カスタムフィールドの必須オプションを false に設定します。
  3. 同意情報を Stripe Webhook を介して取得

    • 支払いが完了したら Webhook を通じて同意情報を取得します。

カスタムフィールドを使った実装方法

カスタムフィールドの追加

Stripe Checkout セッションにカスタムフィールドを追加するには、custom_fields プロパティを設定します。以下は TypeScript を使用した具体例です。

import Stripe from 'stripe';

// Stripe インスタンスの作成
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2022-11-15',
});

export const createCheckoutSession = async (req, res) => {
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price: 'price_1N9yBpEn7pPrX6FGKkjqX78O', // 商品やサービスの価格 ID
          quantity: 1,
        },
      ],
      mode: 'payment',
      success_url: 'https://your-site.com/success',
      cancel_url: 'https://your-site.com/cancel',
      custom_fields: [
        {
          key: 'terms_agreement', // フィールドのキー
          label: {
            type: 'custom',
            custom: 'I agree to the Terms and Conditions',
          },
          type: 'checkbox', // チェックボックス形式
          optional: false, // 必須項目
        },
      ],
    });

    res.json({ url: session.url });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to create Stripe session' });
  }
};
  • custom_fields: カスタムフィールドを定義するプロパティです。
  • key: カスタムフィールドの一意な識別子です。
  • label: 表示されるラベルを指定します。
  • type: checkbox に設定してチェックボックスを表示します。

同意情報の確認

ユーザーが支払いを完了すると、Stripe 側に同意情報が保存されます。Webhook を使用して同意データを取得できます。

Stripe Webhook を使った同意情報の取得

Stripe Webhook は、特定のイベントが発生したときにバックエンドで通知を受け取るための仕組みです。以下は Webhook を使用して同意情報を取得する例です。

import Stripe from 'stripe';
import express from 'express';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2022-11-15',
});

const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body;
  const sig = req.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch (err) {
    console.error(`Webhook Error: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object as Stripe.Checkout.Session;

    // カスタムフィールドの情報を取得
    const termsAgreement = session.custom_fields?.find(
      (field) => field.key === 'terms_agreement'
    )?.value;

    console.log('Terms Agreement:', termsAgreement);

    // 同意情報の処理
    if (termsAgreement === 'true') {
      console.log('User agreed to the terms and conditions.');
    }
  }

  res.status(200).end();
});

app.listen(3000, () => console.log('Webhook server is running on port 3000.'));
  • stripe.webhooks.constructEvent: Webhook イベントを検証します。
  • custom_fields: Checkout セッションのカスタムフィールドデータを取得します。
  • termsAgreement: 利用規約同意の状態を確認します。

以下からフロンエンドとの結合について書いていきます。

フロントエンドとバックエンドの統合例 (Next.js App router)

フロントエンド: Checkout ボタンの作成

// app/checkout/page.tsx

'use client';

import React from 'react';

export default function CheckoutPage() {
  async function handleCheckout() {
    // Server Action 呼び出し
    const response = await fetch('/api/create-checkout-session', {
      method: 'POST',
    });

    if (!response.ok) {
      console.error('Failed to create checkout session');
      return;
    }

    const { url } = await response.json();
    window.location.href = url; // Stripe Checkout セッションにリダイレクト
  }

  return (
    <div className="flex justify-center items-center h-screen">
      <button
        className="bg-blue-500 text-white px-4 py-2 rounded-md"
        onClick={handleCheckout}
      >
        Start Checkout
      </button>
    </div>
  );
}

バックエンド: API Route で Checkout セッション作成

// app/api/create-checkout-session/route.ts

import { NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2022-11-15',
});

export async function POST() {
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price: 'price_1N9yBpEn7pPrX6FGKkjqX78O', // 商品やサービスの価格 ID
          quantity: 1,
        },
      ],
      mode: 'payment',
      success_url: 'https://your-site.com/success',
      cancel_url: 'https://your-site.com/cancel',
      custom_fields: [
        {
          key: 'terms_agreement', // フィールドキー
          label: {
            type: 'custom',
            custom: 'I agree to the Terms and Conditions',
          },
          type: 'checkbox', // チェックボックス形式
          optional: false, // 必須項目
        },
      ],
    });

    return NextResponse.json({ url: session.url });
  } catch (error) {
    console.error('Error creating Stripe Checkout session:', error);
    return NextResponse.json({ error: 'Failed to create Stripe session' }, { status: 500 });
  }
}

API Route を使用して Checkout セッションを作成: /api/create-checkout-session はバックエンドでセッションを生成し、その URL を返します。
Server Actions による直接リダイレクト: フロントエンドで fetch を使ってバックエンドの API を呼び出し、返された URL にリダイレクトします。
API Route を使用して、機密情報 (例: Stripe Secret Key) を安全に保ちながら処理を行います。
Server Components の柔軟性もあります。サーバーサイドとクライアントサイドの統合をシームレスに実現可能です。
リダイレクト処理の簡略化においても、フロントエンドで取得した URL にリダイレクトするだけで完了します。

実装時の注意点

以下の三点に注意が必要です。

  • Webhook 検証: Webhook シークレットを必ず使用してイベントの信頼性を確保します。
  • 利用規約の更新: 利用規約が更新された場合、ユーザーに再同意を求める仕組みが必要です。
  • 同意情報の保存: 同意情報をサーバーに保存しておくと、後から確認できます。

まとめ

Stripe Checkout を活用して利用規約同意を支払いフローに統合する方法を解説しました。カスタムフィールドを利用することで、シームレスなユーザー体験を提供しながら法的要件を満たすことができます。

Stripe の柔軟な API を活用して、より高品質な支払いフローを構築してください!

Discussion