StripeカスタマーポータルをWebアプリに組み込んでみた
リテールアプリ共創部のるおんです。
Webアプリに既にサブスク機能を実装しており、追加で「請求書の確認」「支払い方法の変更」「サブスクの解約/プラン変更」などを安全に任せたいとき、Stripeの カスタマーポータル 機能を組み込むことでローコードで最短で実装できることがわかったので実際にやってみました。
本ブログではダッシュボード設定からAPIでの実装、フロントエンドでのカスタマーポータル表示までを紹介します。
今回はNext.js(App Router + Server Functions)を例にします。
前提・やりたいこと
- 既にサブスク機能を実装している。(顧客IDがDBに保存されている)
- 新しく、「請求書の提供」、「顧客情報の更新」、「決済手段の変更」をStripeのカスタマーポータルを使用して安全かつ最短で実装したい
先に結論
- ダッシュボードで「カスタマーポータル」を有効化し、表示したい機能(請求書、決済情報、顧客情報の更新)をオンにする
- バックエンドでStripeの Customer Portal Session API を呼び出し、返却された
url
へフロントエンドから遷移
これにより、開発者は請求書などの情報をDBにもたせてアプリ側で実装する必要がなくなり、ユーザーがカスタマーポータル上で 請求書の確認、支払い方法の変更、顧客情報の管理 、サブスクの解約/プラン変更 (今回は省略) を行えるようになります。
デモ
詳細
※サンドボックス環境で、テスト用カードを使用
Stripeカスタマーポータルとは
Stripeが提供するセルフサービスの管理画面で、顧客が自分で請求履歴、領収書ダウンロード、カード変更、サブスクのプラン変更・キャンセル等を行えるページです。自前で「マイページ」を作るより圧倒的に早く、安全性や改修コストの面でも有利です。
表示項目はダッシュボードから細かく制御することができ、そこで作成したカスタマーポータルをサーバー側の自前APIでセッションを作成して発行し遷移させることで実現可能です。
やってみた
① ダッシュボードからカスタマーポータルの有効化
- Stripe ダッシュボード > 設定 > Billing > カスタマーポータル に移動
- 「リンクを有効化」をクリック
- 表示したい機能を選択(例: 支払い方法の更新、請求書ダウンロード、プラン変更/キャンセル)して「変更を保存」をクリック
以下の機能を有効化してみました。
- 請求書の履歴
- 顧客情報の更新
- 決済情報の更新
今回はサブスクの更新などは既にアプリ側に実装済みだったため、「プラン変更/キャンセル」は有効化しませんでした。各自のアプリに合わせて有効化の設定をしてください。
② サーバーサイドでカスタマーポータルのセッションを作成
保存済みのユーザーの顧客IDをDBから取得し、StripeのAPIを使用してカスタマーポータルのセッションを作成します。
今回はNext.jsでServer Functionsを使用して簡易的にサーバーサイドの実装作成していますが、APIを作成して実装することももちろん可能です。
"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へと遷移するようにします
"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カスタマーポータルは、課金/請求/支払い情報のセルフサービス化を最小実装で実現できます。UIもStripe側に任せられるため、アプリ側は顧客IDの管理やWebhook同期に集中するだけで簡単に実装可能です。
以上、どなたかの参考になれば幸いです。
参考