freemius-customer-portal について
Freemiusの購入後ポータルをアプリに組み込むためのスキルです。開発者向けに、サブスクリプション、請求情報、解約、請求書を扱うサーバー駆動のエンドポイントとReactコンポーネントを案内します。
主な機能
- 01ポータル要求用の単一サーバーエンドポイント
- 02サブスクリプション表示とライセンスアップグレード
- 03継続特典クーポンを伴う解約フロー
- 04請求情報の更新と請求書PDF
活用シーン
- 01セルフサービスのサブスクリプション管理追加
- 02更新解約前のクーポン提示
- 03顧客による請求情報の更新
- 04支払い履歴の表示と請求書ダウンロード
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ freemius/ freemius-ai --skill freemius-customer-portalインストール後、Claude Code で/freemius-customer-portalを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/billing.mdcancellation-and-coupon.mdheadless-endpoint.mdpayments-and-invoices.mdsubscription-and-upgrade.md詳細
Freemius カスタマーポータル
サインインしたユーザーが購入後のすべてを管理できるページを用意します。サブスクリプション、アップグレード、キャンセル(継続特典付き)、請求先情報、請求書を管理でき、アプリ側で請求ロジックを再実装する必要はありません。
出典:React Starter Kit Customer Portal コンポーネント https://freemius.com/help/documentation/saas-sdk/react-starter/components/#customer-portal-component
フレームワークに関する注意。 この Skill のルート例は Hono 用に記述されていますが、SDK のポータルプロセッサーは Web Standard の
Request/Responseルーティングに対応するあらゆるフレームワークで動作します。アプリで実際に使用している HTTP フレームワークに合わせて、シンプルなルートを調整してください。React Starter Kit のCustomerPortalコンポーネントは React 19+ と shadcn UI + Tailwind を対象としています(npx shadcn@latest add https://shadcn.freemius.com/all.jsonでインストールしてください)。アプリで shadcn/React を使用していない場合は、参考実装として利用してください。
サンドボックス。 検証中は、プロセッサーの
isSandboxを専用のFREEMIUS_SANDBOX環境変数に接続してください(NODE_ENVは絶対に使用しないでください)。これにより、チェックアウトのサンドボックスモードと一致します。サンドボックスはテスト用クレジットカードを使用するチェックアウトモードで、freemius-coreが管理しています。
守るべき重要なルール
- ポータルはヘッドレスで、サーバー主導です。 SDK には
PortalRequestProcessorが用意されています。バックエンドでエンドポイントを 1 つ(GET|POST /api/portal)公開すると、SDK がデータの取得と、署名付きのアクションごとの URL を介したすべてのアクションを処理します。コンポーネントが Freemius に直接アクセスすることはありません。 - アクションにはトークン署名が付与されます。 ポータルデータには署名済みのアクション URL(キャンセル、クーポン、請求先情報の更新、請求書)が含まれます。コンポーネントはそれらの URL にアクセスするだけで、SDK がサーバー側でトークンを検証します。認証済みユーザーの識別情報を渡すだけでかまいません。(アップグレードは例外で、サーバー側で構築したライセンスアップグレード用チェックアウト(
setLicenseUpgradeByKey)を経由します。これにより、二重サブスクリプションや更新中のライセンスで発生する 500 エラーを回避できます。) - ユーザーの識別情報はアプリ側で管理し、それ以外はすべて SDK が処理します。 プロセッサーの
getUserは、セッションから Freemius ユーザーを特定します(たとえば、メールアドレスから特定します)。Freemius の認証情報はすべてサーバー側に保持されます。 - キャンセルの前に、まず継続を促してください。 更新キャンセル用のクーポンが利用可能な場合は、キャンセルを完了する前に提示してください。
1 つのエンドポイント
// src/server/services/freemius/portal.ts
import { freemius, IS_SANDBOX } from './client';
import { env } from '../../env';
const PORTAL_ENDPOINT = `${env.publicAppUrl}/api/portal`;
export async function processPortalRequest(
request: Request,
email: string
): Promise<Response> {
const processor = freemius.customerPortal.request.createProcessor({
portalEndpoint: PORTAL_ENDPOINT, // must match the public URL the browser hits
isSandbox: IS_SANDBOX,
getUser: async () => {
const fsUser = await freemius.api.user.retrieveByEmail(email);
return fsUser?.id ? { id: String(fsUser.id), email } : { email };
},
});
return processor(request); // dispatches by ?action= and verifies tokens
}
// src/server/routes/portal.ts — route stays thin: auth, then delegate.
portalRoute.on(['GET', 'POST'], '/', requireUser, async (c) =>
processPortalRequest(c.req.raw, c.get('user').email)
);
プロセッサーは ?action= クエリパラメーターに基づいて処理を振り分けます。
| メソッド | ?action= | 結果 |
|---|---|---|
| GET | portal_data | サブスクリプション+請求先情報+支払い+署名付き URL |
| POST | billing | 請求先情報の更新 |
| POST | subscription_cancellation | 更新のキャンセル(任意のフィードバック付き) |
| POST | subscription_cancellation_coupon | 継続クーポンの適用 |
| GET | invoice | 請求書 PDF をストリーミング配信 |
references/headless-endpoint.md を参照してください。
コンポーネント
CustomerPortal を、編集可能な shadcn/ui スタイルの React ソースとして実装します。?action=portal_data を一度取得し、そのデータに組み込まれた署名付き URL を使って、次の 4 つのセクションを表示します。
| セクション | ユーザーができること | リファレンス |
|---|---|---|
| サブスクリプション | 現在のプランを確認し、サーバー側で構築された ライセンスアップグレードのチェックアウト(setLicenseUpgradeByKey)を使ってアップグレードします。これは二重サブスクリプションや更新中のライセンスで発生する 500 エラーを回避できる堅牢な方法です。 | references/subscription-and-upgrade.md |
| キャンセル | 更新をキャンセルします — 免責事項 → 継続特典クーポン → フィードバック | references/cancellation-and-coupon.md |
| 請求情報 | 事業者名/納税者番号/住所を編集します | references/billing.md |
| 支払いと請求書 | 支払い履歴を確認し、請求書の PDF をダウンロードします | references/payments-and-invoices.md |
クイックスタート(フロントエンド)
import { CustomerPortal } from './react-starter/components/customer-portal';
import { PORTAL_ENDPOINT, portalFetch } from './api';
// portalFetch attaches the auth header; the component does the rest.
<CustomerPortal endpoint={PORTAL_ENDPOINT} fetcher={portalFetch} />;
// Loading portal data:
const res = await fetcher(`${endpoint}?action=portal_data`);
const data = (await res.json()) as PortalData;
// data.subscriptions.{primary,active,past}, data.billing, data.payments,
// data.cancellationCoupons — each carries signed action URLs.
基本的な考え方
Frontend <CustomerPortal> Backend (one endpoint) Freemius
GET /api/portal?action=portal_data ─▶ processPortalRequest()
{ subscriptions, billing, payments, ◀─ freemius.customerPortal.request
+ signed action URLs } .createProcessor() → getUser()
POST <signed cancelRenewalUrl> ───────▶ processor verifies token ──────────▶ cancel renewal
POST <signed couponUrl> ──────────────▶ processor verifies token ──────────▶ apply coupon
POST <signed billing updateUrl> ──────▶ processor verifies token ──────────▶ update billing
GET <signed invoiceUrl> ─────────────▶ processor verifies token ──────────▶ invoice PDF
GET /api/checkout/upgrade ───────────▶ license.retrieve + checkout.create()
{ link } ◀──────────────────────────── .setLicenseUpgradeByKey(secret_key) ─▶ plan-change
.getLink() — browser navigates ─────▶ checkout
⚠️ アップグレードには、上記のサーバー側で構築されたライセンスアップグレードのチェックアウトを使ってください。また、これには
checkout.create({ licenseId })を使わないでください。更新が有効なライセンスでは、このオプションは 500 エラーになります(SDK 内部で支払い方法更新エンドポイントを呼び出しますが、このエンドポイントはexpiration: nullのライセンスを「無期限」として拒否します)。ライセンスをサーバー側で取得し、代わりにsetLicenseUpgradeByKey(license.secret_key)を使ってください。詳しくは、references/subscription-and-upgrade.md を参照してください。
ダッシュボードの設定(担当者に依頼)
- 製品 → クーポン → 更新キャンセル(継続特典)クーポンを作成し、
cancellationCouponsにデータが設定され、ウィザードで提示できるようにします。 - ローカルのエンタイトルメントを同期する Webhook とリダイレクトの設定は
freemius-coreが管理します。アップグレード/キャンセルは Webhook イベント(license.plan.changed、license.cancelledなど)としてここに届きます。
アクション後の同期(変更がアプリに反映される仕組み)
ポータルでのアクションは、まず Freemius 側の状態を変更します。その後、ローカルの user_fs_entitlement ミラーは次の 2 つの方法で更新されます。
- ポータル UI では即時に反映 — アクションの後に
?action=portal_dataを再取得します(Freemius の最新データを読み込みます)。 - エンタイトルメントミラーでは —
freemius-coreの Webhook を通じて更新されます(アップグレード時はlicense.plan.changed、キャンセル時はlicense.cancelled/有効期限切れ)。⚠️ ダッシュボードで Webhook URL を設定または変更した後、反映まで 約 5 分かかります。URL が有効になる前に発生したイベントは配信されず、後から再送されることもありません。詳しくはfreemius-coreのreferences/webhooks.md(イベントの取りこぼしが疑われる場合はfreemius-troubleshooting)を参照してください。