上級編のカリキュラムへ

上級編40

上級② 有料プランの出し分けを作る

ペイウォールの完成。まさにこのプレミアム講座そのものの作り方

①で支払いは通るようになりました。今日は「払った人にだけ、特別な画面を出す」を作ります。種明かしをすると——いまあなたが読んでいるこのプレミアム講座が、まさに今日作る仕組みそのものです。

今日のゴール

テスト決済をすると自動でプレミアム会員になり、会員だけのページが見えること。部品は3つ:①プラン列(会員台帳)②Webhook(Stripeからの自動連絡)③出し分け(サーバー側での判定)。

STEP1:会員台帳に“プラン”の列を作る

SupabaseのSQL Editorで実行。ブラウザからは書き換えられないようにするのが急所です(自分で自分をプレミアムにできたら、課金の意味がなくなります)。

alter table public.profiles
  add column if not exists plan text not null default 'free';

-- ブラウザ(anon/authenticated)からの書き換えを禁止し、
-- 本人が変更してよい列だけを開け直す
revoke update on public.profiles from anon, authenticated;
grant update (nickname) on public.profiles to authenticated;

ここを最重要ポイントとして覚えてください

plan のような“権利”の列は、列単位で書き込み禁止にします。RLSで「自分の行だけ」を守っていても、自分の行のplanを自分で書き換えられたら意味がない——行の守り(RLS)と列の守り(権限)はセットです。

STEP2:Webhook——「入金あり」をStripeが知らせに来る

決済が終わった“後”に、誰がどうやってplanを書き換えるのか?答えは Webhook(ウェブフック)——Stripeのサーバーが、あなたのサーバーの専用窓口に「この人の支払いが完了しました」と直接連絡してくる仕組みです。

「successページに戻ってきたら昇格」ではダメなのか?と思いますよね。①支払い後にページを閉じたら昇格されない ②successのURLを直接開くだけで昇格できてしまう——画面は偽装できるが、サーバー同士の連絡は署名で検証できる。お金の確定処理はWebhookでやる、が世界標準です。

STEP3:金庫室の鍵——service_role、初めて触ります

Webhookには難題があります。連絡してくるのはStripeであって、本人(ログインユーザー)ではない。つまり「自分の行だけ」のRLSでは書き換えられません。ここで初めて、初級④で「絶対に触るな」と言った service_role キー(RLSを超える全権鍵)を使います。

あのとき触るなと言った理由
全権鍵は、ブラウザに出たら全データが他人に渡るから。
今日から使ってよい理由
Webhookの処理はサーバーの奥(Route Handler)だけで動く。金庫の鍵は金庫室の中でだけ使う、なら安全。
  1. SupabaseのProject Settings → API から service_role キーをコピー。
  2. Vercelの環境変数 SUPABASE_SERVICE_ROLE_KEY に追加(NEXT_PUBLICなし。等級は“全権”——最高機密です)。

STEP4:AIにWebhookを実装してもらう

Stripe決済とアカウントを接続したい。①Checkoutセッション作成時に、ログイン中ユーザーのIDを metadata に入れる ②/api/stripe/webhook のRoute Handlerを作り、checkout.session.completed イベントを受けたら、metadataのユーザーIDの profiles.plan を premium に更新する ③Webhookは環境変数 STRIPE_WEBHOOK_SECRET で署名検証して、検証に失敗したら処理しない ④planの更新には SUPABASE_SERVICE_ROLE_KEY を使うが、このキーはこのRoute Handlerのサーバー専用ファイルでだけ使い、ブラウザ側のコードには絶対に出さない。まず計画を見せて。
  1. Stripeダッシュボード「開発者」→「Webhook」→「エンドポイントを追加」。
  2. URLは https://あなたの公開URL/api/stripe/webhook、イベントは checkout.session.completed を選択。
  3. 発行された署名シークレット(whsec_…)を環境変数 STRIPE_WEBHOOK_SECRET に追加→Redeploy。

STEP5:出し分け(ペイウォール)を作る

/premium ページを作って、サーバー側で profiles.plan を判定して出し分けて。premium の人には限定コンテンツ、free の人には①で作った /upgrade への案内(セールスページ)を表示。判定は必ずサーバー側で行い、ブラウザ側の判定だけに頼らないこと。まず計画を見せて。

なぜ“サーバー側で”としつこく言うのか

ブラウザ側の出し分けは、開発者ツールで剥がせることがあります。本当の門番はサーバー側(とRLS)に置き、ブラウザの表示はあくまで案内——このサイトのプレミアムも同じ構えです。

STEP6:通しでテスト——昇格の瞬間を見る

  1. テスト用の無料アカウントでログイン→ /premium がセールスページになっている。
  2. /upgrade からテストカードで決済。
  3. /premium を開き直す→限定コンテンツが見えたら、ペイウォール完成!(SupabaseのTable Editorで plan が premium に変わったのも見ておきましょう)
  4. 仕上げの儀式:別の無料アカウントでは見えないことも確認(2アカウント確認、ここでも)。

おさらい:今日使った“言葉”

Webhook
サービス間の自動連絡。お金の確定処理は画面ではなくこれで。
署名検証(whsec_)
連絡が本物のStripeからかを確かめる仕組み。検証なしのWebhookは偽装され放題。
service_role
RLSを超える全権鍵。サーバーの奥でだけ。NEXT_PUBLIC厳禁。
ペイウォール
払った人だけが通れる壁。判定はサーバー側で。

上級②・完成チェック

0 / 4

つまずいたら

①決済してもplanが変わらない→Stripeダッシュボードの該当Webhookの「配信履歴」を見る。エラーが出ていたらその内容をAIへ。②署名検証エラー→whsec_の値とRedeployを確認。③ローカルで試したい→今はデプロイ先で確認すればOK(StripeはローカルPCに連絡できないため)。

お疲れさまでした!!

課金つきの会員制サービス——個人開発の一つの到達点です。次回③は、これを堂々と売るための法務ページ3点セット。実は売る側の義務でもあります。