※本記事には広告(A8・楽天アフィリエイト)リンクが含まれる場合があります。

個人開発でプロダクトを作ったはいいものの、「決済をどう組み込めばいいかわからない」と手が止まっていませんか?

Stripeは、2026年現在もっとも個人開発者に選ばれている決済プラットフォームです。クレジットカード情報を自分のサーバーで扱う必要がなく、数十行のコードでサブスクリプション課金を導入できます。この記事では、Stripeの基本概念から実際のTypeScript実装コードまで、個人開発者が最速で決済機能を導入するための実践ガイドをお届けします。

なぜ個人開発でStripeを選ぶのか

決済サービスはStripe以外にもPAY.JP、Square、Lemon Squeezyなどがありますが、個人開発においてStripeが圧倒的に選ばれる理由があります。

  • 初期費用ゼロ、従量課金のみ — 月額固定費がなく、決済が発生した分(3.6%)だけ手数料がかかるモデル。売上ゼロの段階でもリスクなしで導入できます。
  • ドキュメントが圧倒的に充実 — 日本語ドキュメントも整備されており、APIリファレンスの質は業界最高水準。迷ったらドキュメントを読めば解決します。
  • Checkout・Customer Portal・Billing Portal — 決済画面、顧客管理、サブスク管理のUIをStripeが提供してくれるため、自前で決済フォームを作る必要がありません。
  • テスト環境が完備 — テスト用APIキーとテスト用カード番号が用意されており、本番と同じフローを無料で何度でも試せます。
  • Webhookによるイベント駆動 — 決済完了・解約・更新失敗などのイベントをリアルタイムで受け取れるため、サーバーレス環境とも相性抜群です。

特にCloudflare WorkersSupabaseと組み合わせれば、月額$0でサブスク課金付きのSaaSを運用開始できてしまいます。

Stripe導入の全体像を理解する

Stripeで決済を実装する際の基本フローは以下の通りです。

サブスクリプション課金の流れ

  1. ユーザーが「アップグレード」ボタンを押す → フロントエンドからAPIを呼ぶ
  2. サーバーがCheckout Sessionを作成 → StripeがホストするURLを返す
  3. ユーザーがStripe決済画面でカード情報を入力 → Stripeが処理
  4. 決済完了後、StripeがWebhookでサーバーに通知 → DBを更新
  5. ユーザーのプランがProに切り替わる

ポイントは、カード情報を自分のサーバーで一切扱わないこと。Stripeの決済画面(Checkout)に遷移させることで、PCI DSSコンプライアンスの負担をほぼゼロにできます。

必要なStripeオブジェクト

  • Product — 販売する商品(例:「Proプラン」)
  • Price — 商品の価格と課金間隔(例:月額980円)
  • Customer — 顧客情報(ユーザーと1対1で紐づける)
  • Subscription — 顧客とPriceを紐づけた継続課金
  • Checkout Session — 決済画面を生成するためのセッション

ProductとPriceはStripeダッシュボードでGUI作成できるので、コードでの管理は不要です。

広告・楽天アフィリエイト

開発者向けガジェット

PC周辺機器を楽天市場でチェック。

楽天ポイント還元

楽天市場を見る →

広告・楽天アフィリエイト

メカニカルキーボード

打鍵感で選ぶ開発者向けキーボード。

楽天ポイント還元

キーボードを楽天で見る →

実装ステップ1:Stripeのセットアップ

まずはStripeアカウントを作成し、開発環境を準備しましょう。

SDKのインストール

npm install stripe
# 型定義は stripe パッケージに同梱されています

Stripeクライアントの初期化

// lib/stripe.ts
import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2026-02-24',  // 最新のAPI Versionを指定
  typescript: true,
});

APIキーはStripeダッシュボードの「開発者」→「APIキー」から取得します。テスト環境では sk_test_ で始まるキーを使うこと。本番の sk_live_ キーは環境変数で厳重に管理し、絶対にクライアントサイドに露出させないでください。

商品と価格の作成

Stripeダッシュボードで商品を作成します。

  1. 「商品カタログ」→「商品を追加」
  2. 商品名:「Proプラン」
  3. 価格:月額980円(recurring / monthly)
  4. 作成後に表示される price_xxxx をメモ

実装ステップ2:Checkout Sessionの作成

ユーザーが「アップグレード」を押したときに、Stripeの決済画面を生成するAPIを作ります。

// api/create-checkout.ts
import { stripe } from '../lib/stripe';

export async function handleCreateCheckout(
  userId: string,
  userEmail: string,
  priceId: string
) {
  // Stripeの顧客を取得または作成
  let customer: Stripe.Customer;
  const existing = await stripe.customers.list({
    email: userEmail,
    limit: 1,
  });

  if (existing.data.length > 0) {
    customer = existing.data[0];
  } else {
    customer = await stripe.customers.create({
      email: userEmail,
      metadata: { userId },  // 自分のDBのユーザーIDを紐づけ
    });
  }

  // Checkout Sessionを作成
  const session = await stripe.checkout.sessions.create({
    customer: customer.id,
    payment_method_types: ['card'],
    mode: 'subscription',
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.APP_URL}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.APP_URL}/billing`,
    metadata: { userId },
    subscription_data: {
      metadata: { userId },  // Subscriptionにもメタデータを入れる
    },
  });

  return session.url;  // ← このURLにリダイレクト
}

metadataに自分のアプリのユーザーIDを含めるのが重要です。Webhook受信時に「誰の決済か」を特定するために使います。

実装ステップ3:Webhookで決済イベントを処理する

Stripeの決済画面で支払いが完了しても、自分のサーバーは直接それを知ることができません。ここで使うのがWebhookです。

// api/webhook.ts
import { stripe } from '../lib/stripe';

export async function handleWebhook(
  rawBody: string,
  signature: string
) {
  // 署名検証(必須!偽リクエスト防止)
  const event = stripe.webhooks.constructEvent(
    rawBody,
    signature,
    process.env.STRIPE_WEBHOOK_SECRET!
  );

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      const userId = session.metadata?.userId;
      const subscriptionId = session.subscription as string;

      // DBにサブスクリプション情報を保存
      await db.update('users', {
        where: { id: userId },
        data: {
          plan: 'pro',
          stripeCustomerId: session.customer as string,
          stripeSubscriptionId: subscriptionId,
        },
      });
      break;
    }

    case 'customer.subscription.deleted': {
      const subscription = event.data.object as Stripe.Subscription;
      const userId = subscription.metadata?.userId;

      // プランをFreeに戻す
      await db.update('users', {
        where: { id: userId },
        data: { plan: 'free', stripeSubscriptionId: null },
      });
      break;
    }

    case 'invoice.payment_failed': {
      const invoice = event.data.object as Stripe.Invoice;
      // 決済失敗時の通知処理(メール送信など)
      console.log(`Payment failed for customer: ${invoice.customer}`);
      break;
    }
  }

  return { received: true };
}

constructEvent による署名検証は絶対に省略しないでください。これがないと、第三者が偽のWebhookリクエストを送ってプランを不正に変更できてしまいます。

ローカルでWebhookをテストする

Stripe CLIを使えば、ローカル環境でもWebhookイベントを受信できます。

# Stripe CLIのインストール
brew install stripe/stripe-cli/stripe

# ログイン
stripe lo
        gin

# Webhookのフォワーディング
stripe listen --forward-to localhost:3000/api/webhook

# テストイベントの送信
stripe trigger checkout.session.completed

これにより、本番デプロイ前にWebhookの処理を完全にテストできます。

広告・楽天アフィリエイト

PCモニター

デュアルディスプレイや高解像度モデルをチェック。

楽天ポイント還元

モニターを楽天で見る →

広告・楽天アフィリエイト

プログラミング技術書

実践寄りの技術書・参考書を探す。

楽天ポイント還元

技術書を楽天で見る →

実装ステップ4:カスタマーポータルで解約・プラン変更に対応

サブスクの管理画面を自前で作るのは大変です。Stripeのカスタマーポータルを使えば、解約・プラン変更・カード情報更新をStripeに任せられます。

// api/customer-portal.ts
export async function handleCustomerPortal(stripeCustomerId: string) {
  const session = await stripe.billingPortal.sessions.create({
    customer: stripeCustomerId,
    return_url: `${process.env.APP_URL}/settings`,
  });

  return session.url;  // ← このURLにリダイレクト
}

たった数行で、プロフェッショナルなサブスク管理画面が手に入ります。カスタマーポータルの設定(解約を許可するかなど)はStripeダッシュボードから細かく調整できます。

Cloudflare Workersで動かす場合の注意点

Hono + Cloudflare Workersで決済APIを構築する場合、いくつか気をつける点があります。

  • Node.js互換モードを有効にするwrangler.tomlcompatibility_flags = ["nodejs_compat"] を追加。Stripe SDKが内部でNode.js APIを使うため必須です。
  • Webhook署名検証でrawBodyを使う — Honoの c.req.text() でraw bodyを取得してから constructEvent に渡すこと。JSONパース後の文字列では署名が一致しません。
  • Secret は wrangler secret で管理STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETwrangler secret put で設定。.env ファイルに書かない。
// wrangler.toml
name = "my-saas-api"
main = "src/index.ts"
compatibility_date = "2026-04-01"
compatibility_flags = ["nodejs_compat"]

よくあるハマりポイントと対処法

Webhook が届かない

Stripeダッシュボードの「開発者」→「Webhook」からイベント配信ログを確認できます。URLの設定ミスや、サーバーが20 0以外を返しているケースが多いです。StripeはWebhookの再送を最大3日間行うので、一時的なエラーなら自動復旧します。

テスト環境と本番環境の混同

sk_test_sk_live_ のキーを混在させると、顧客データやサブスクリプションが別空間になって混乱します。環境変数を .env.development.env.production で分離し、デプロイ時に確認する習慣をつけましょう。

二重課金の防止

Checkout Session作成時にべき等性キー(idempotencyKey)を指定するか、ユーザーのサブスクリプション状態を事前チェックして、すでにProプランのユーザーには新たなCheckoutを作らないようにしましょう。

決済導入後にやるべきこと

Stripeの実装が完了したら、以下も忘れずに対応しましょう。

  • 特定商取引法に基づく表記 — 日本で有料サービスを提供する場合は必須。販売者名、連絡先、返金ポリシーなどを記載したページを用意します。
  • プライバシーポリシーの更新 — 決済情報の取り扱いについて追記。「カード情報はStripeが管理し、当サービスでは保持しません」と明記すればOKです。
  • 監視とアラート — 決済失敗率やチャージバック率の監視を設定。PagePulseのようなモニタリングツールでAPIのダウンタイムも監視しておくと安心です。
  • 領収書の自動送信 — Stripeダッシュボードで「レシートの自動送信」を有効にしておくと、顧客対応の手間が減ります。

広告・楽天アフィリエイト

ガジェット・オーディオ

イヤホン・便利ガジェットをポイント還元で。

楽天ポイント還元

ガジェットを楽天で見る →

まとめ

Stripe決済の実装は、個人開発者にとって「最初は怖いが、やってみると意外と簡単」な領域です。要点を整理すると:

  • Checkout Sessionで決済画面を作り、カード情報は一切触らない
  • Webhookで決済完了を検知し、DBを更新する
  • カスタマーポータルで解約・変更はStripeに任せる
  • 署名検証は省略しない(セキュリティの生命線)
  • ローカルテストはStripe CLIで完結する

この5つを押さえれば、個人開発の収益化で最大のハードルである「決済実装」をクリアできます。まずはテスト環境で動かしてみてください。実際にクレジットカードの決済が通る瞬間は、個人開発者にとって何よりのモチベーションになるはずです。

📌 本番環境のサーバーを探しているなら

Stripeとの連携実績も豊富な楽天市場で見るなら、高速・安定のサーバー環境でSaaSを運用できます。Cloudflare Workersと併用するハイブリッド構成にも対応。