StripeカスタマーポータルをWebアプリに組み込んでみた

StripeカスタマーポータルをWebアプリに組み込んでみた

2025.09.23

リテールアプリ共創部のるおんです。

Webアプリに既にサブスク機能を実装しており、追加で「請求書の確認」「支払い方法の変更」「サブスクの解約/プラン変更」などを安全に任せたいとき、Stripeの カスタマーポータル 機能を組み込むことでローコードで最短で実装できることがわかったので実際にやってみました。

https://docs.stripe.com/customer-management?locale=ja-JP

本ブログではダッシュボード設定からAPIでの実装、フロントエンドでのカスタマーポータル表示までを紹介します。
今回はNext.js(App Router + Server Functions)を例にします。

前提・やりたいこと

  • 既にサブスク機能を実装している。(顧客IDがDBに保存されている)
  • 新しく、「請求書の提供」、「顧客情報の更新」、「決済手段の変更」をStripeのカスタマーポータルを使用して安全かつ最短で実装したい

先に結論

  • ダッシュボードで「カスタマーポータル」を有効化し、表示したい機能(請求書、決済情報、顧客情報の更新)をオンにする
  • バックエンドでStripeの Customer Portal Session API を呼び出し、返却されたurlへフロントエンドから遷移

これにより、開発者は請求書などの情報をDBにもたせてアプリ側で実装する必要がなくなり、ユーザーがカスタマーポータル上で 請求書の確認、支払い方法の変更、顧客情報の管理 、サブスクの解約/プラン変更 (今回は省略) を行えるようになります。

デモ
stripe-customer-portal-demo

詳細
スクリーンショット 2025-09-22 23.50.41

※サンドボックス環境で、テスト用カードを使用

Stripeカスタマーポータルとは

https://docs.stripe.com/customer-management?locale=ja-JP

Stripeが提供するセルフサービスの管理画面で、顧客が自分で請求履歴、領収書ダウンロード、カード変更、サブスクのプラン変更・キャンセル等を行えるページです。自前で「マイページ」を作るより圧倒的に早く、安全性や改修コストの面でも有利です。

表示項目はダッシュボードから細かく制御することができ、そこで作成したカスタマーポータルをサーバー側の自前APIでセッションを作成して発行し遷移させることで実現可能です。

やってみた

① ダッシュボードからカスタマーポータルの有効化

https://docs.stripe.com/customer-management/activate-no-code-customer-portal

  1. Stripe ダッシュボード > 設定 > Billing > カスタマーポータル に移動
  2. リンクを有効化」をクリック
  3. 表示したい機能を選択(例: 支払い方法の更新、請求書ダウンロード、プラン変更/キャンセル)して「変更を保存」をクリック

スクリーンショット 2025-09-22 23.02.46

以下の機能を有効化してみました。

  • 請求書の履歴
  • 顧客情報の更新
  • 決済情報の更新

今回はサブスクの更新などは既にアプリ側に実装済みだったため、「プラン変更/キャンセル」は有効化しませんでした。各自のアプリに合わせて有効化の設定をしてください。

② サーバーサイドでカスタマーポータルのセッションを作成

https://docs.stripe.com/customer-management/integrate-customer-portal
保存済みのユーザーの顧客IDをDBから取得し、StripeのAPIを使用してカスタマーポータルのセッションを作成します。

今回はNext.jsでServer Functionsを使用して簡易的にサーバーサイドの実装作成していますが、APIを作成して実装することももちろん可能です。

src/app/actions/createCustomerPortalSession.ts
			
			"use server";
import { stripe } from "@/lib/stripe"; // 初期化済みのstripeクライアント

/**
 * DBからユーザー情報を取得してstripeの顧客IDを返す
 */
const getUserStripeCustomerId = async (userId: string) => {
  // 例)セッションのuserIdからDB検索してcustomerIdを返す. 実装は各自のアプリのDBに合わせて置き換えてください
  const user = await userRepository.findById(userId);

  if (!user?.stripeCustomerId) {
    // Stripe顧客IDが存在しない場合は新規作成処理など...
  }

  return user.stripeCustomerId;
}

/**
 * カスタマーポータルのセッションを作成して返す
 */
export async function createCustomerPortalSession() {
  const session = await auth();
  const userId = session?.user?.id;
  if (!userId) throw new Error("Unauthorized");

  const customerId = await getUserStripeCustomerId(userId);

+ const portalSession = await stripe.billingPortal.sessions.create({
+   customer: customerId, // 顧客ID
+   return_url: `${process.env.NEXT_PUBLIC_APP_URL}/account/billing`, // カスタマーポータルから戻ってきたときに遷移したい画面のURL
+ });

  return { url: portalSession.url };
}

		

③ フロントエンドで返却値のURLへ遷移

フロントエンド側でServer Functionsを呼び出してサーバーサイドのコードを実行し、カスタマーポータルのurlへと遷移するようにします

src/app/account/billing/page.tsx
			
			"use client";

import { Button } from "../common/shadcn/button";
import { createCustomerPortalSession } from "@/app/actions/createCustomerPortalSession";

export default function BillingPage() {
	// ポータルへの遷移処理
	const handleOpenCustomerPortal = async () => {
+		const { url } = await createCustomerPortalSession();
+		if (url) {
+			window.location.href = url;
+		}
	};

	return (
		<div>
			{/* 省略 */}
			<Button
+				onClick={handleOpenCustomerPortal}
				className="mt-2 border px-3 py-2 rounded"
			>
				お客様情報の管理と請求書
			</Button>
		</div>
	);
}


		

動作確認

アプリの「カスタマーポータルを開く」ボタンを押すことでStripeのカスタマーポータルに遷移し、決済情報や顧客情報の確認/更新、請求書の履歴が表示されることを確認します。(サンドボックス環境で、テスト用カードを使用)

デモ
stripe-customer-portal-demo

詳細
スクリーンショット 2025-09-22 23.50.41

終わりに

Stripeカスタマーポータルは、課金/請求/支払い情報のセルフサービス化を最小実装で実現できます。UIもStripe側に任せられるため、アプリ側は顧客IDの管理やWebhook同期に集中するだけで簡単に実装可能です。

以上、どなたかの参考になれば幸いです。

参考

https://docs.stripe.com/customer-management?locale=ja-JP
https://docs.stripe.com/api/customer_portal/sessions/create

この記事をシェアする

FacebookHatena blogX

関連記事