subscriptions-best-practices について
PayKit でサブスクリプションを実装する開発者向けのスキルです。状態に応じたプラン変更、決済、ライフサイクル更新、顧客の請求管理を、サーバーとクライアント双方の例で説明します。
主な機能
- 01現在の状態に応じた変更処理
- 02アップグレード、ダウングレード、解約、再開
- 03サーバーとクライアントの使用例
- 04状態確認と決済画面への遷移
- 05Webhook と顧客ポータルの説明
活用シーン
- 01ユーザーのプラン開始や変更
- 02アプリへのサブスクリプション状態同期
- 03決済画面へのユーザー誘導
- 04顧客による支払い方法や請求書の管理
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ getpaykit/ skills --skill subscriptions-best-practicesインストール後、Claude Code で/subscriptions-best-practicesを入力して読み込まれたか確認します。
詳細
サブスクリプション
PayKit はすべてのサブスクリプションの切り替えに、単一の subscribe() メソッドを使用します。
subscribe()
const result = await paykit.subscribe({
customerId: "user_123",
planId: "pro",
successUrl: "https://example.com/billing?success=true",
cancelUrl: "https://example.com/billing",
})
パラメーター
| パラメーター | 必須 | 説明 |
|---|---|---|
customerId | はい | アプリのユーザー ID |
planId | はい | 対象のプラン ID(型安全) |
successUrl | はい | チェックアウト成功後のリダイレクト先 |
cancelUrl | いいえ | ユーザーがチェックアウトをキャンセルした場合のリダイレクト先 |
forceCheckout | いいえ | 支払い方法が登録済みでも、常にチェックアウトへリダイレクトします |
戻り値
interface SubscribeResult {
paymentUrl: string | null
invoice?: {
providerInvoiceId: string
hostedUrl: string | null
currency: string
totalAmount: number
status: string | null
}
requiredAction?: {
type: string
paymentIntentId?: string
clientSecret?: string
} | null
}
切り替えの動作
subscribe() は、顧客の現在の状態に基づいて適切な処理を判断します。
| 現在の状態 | 対象 | 動作 |
|---|---|---|
| サブスクリプションなし | 無料プラン | 即時に有効化します |
| サブスクリプションなし | 有料プラン、支払い方法なし | Stripe Checkout 用の paymentUrl を返します |
| サブスクリプションなし | 有料プラン、支払い方法あり | サブスクリプションを直接作成します |
| 無料プラン利用中 | 有料プラン | チェックアウトまたはサブスクリプションを直接作成します |
| 有料プラン利用中 | 同じグループのより高価格なプラン | アップグレード:即時に切り替えます |
| 有料プラン利用中 | 同じグループのより低価格なプラン | ダウングレード:期間終了時に適用するよう予約します |
| 有料プラン利用中 | 同じグループの無料プラン | キャンセル:期間終了時に適用するよう予約します |
| キャンセル待ち | 同じプラン | 再開:キャンセルを取り消します |
| ダウングレード待ち | 現在のプラン | 再開:予約された変更を取り消します |
| 現在のプラン利用中 | 同じプラン、保留中の変更なし | 何もしません |
サーバーサイドでの使用
// New subscription (may require checkout)
const { paymentUrl } = await paykit.subscribe({
customerId: "user_123",
planId: "pro",
successUrl: "/billing?success=true",
})
if (paymentUrl) {
// Redirect user to Stripe Checkout
redirect(paymentUrl)
}
// Upgrade (immediate, no checkout needed)
await paykit.subscribe({
customerId: "user_123",
planId: "enterprise",
successUrl: "/billing",
})
// Downgrade (scheduled for period end)
await paykit.subscribe({
customerId: "user_123",
planId: "free",
successUrl: "/billing",
})
// Resume a pending cancellation
await paykit.subscribe({
customerId: "user_123",
planId: "pro", // same plan they're on
successUrl: "/billing",
})
クライアントサイドでの使用
identify によって解決されるため、クライアント SDK では customerId を省略します。
import { paykitClient } from "@/lib/paykit-client"
// Subscribe to a plan
await paykitClient.subscribe({
planId: "pro",
successUrl: `${window.location.origin}/billing?success=true`,
cancelUrl: `${window.location.origin}/billing`,
})
// Open Stripe Customer Portal (manage payment methods, invoices)
await paykitClient.customerPortal({
returnUrl: window.location.href,
})
サブスクリプションの状態を取得する
const customer = await paykit.getCustomer({ id: "user_123" })
for (const sub of customer.subscriptions) {
console.log(sub.planId) // "pro"
console.log(sub.status) // "active", "canceled", etc.
console.log(sub.cancelAtPeriodEnd) // true if pending cancellation
console.log(sub.currentPeriodEnd) // when the current period ends
}
チェックアウトのリダイレクト処理
subscribe() が paymentUrl を返したら、支払いを完了できるようユーザーをリダイレクトします。
サーバーコンポーネント/API ルート:
import { redirect } from "next/navigation"
const { paymentUrl } = await paykit.subscribe({
customerId: userId,
planId: "pro",
successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/billing?success=true`,
})
if (paymentUrl) {
redirect(paymentUrl)
}
クライアントサイド:
const result = await paykitClient.subscribe({
planId: "pro",
successUrl: `${window.location.origin}/billing?success=true`,
})
paymentUrl が返された場合、クライアント SDK がリダイレクトを自動的に処理します。
Webhook
Stripe はサブスクリプションのライフサイクルイベントについて Webhook を送信します。PayKit はルートハンドラーを介してこれらを自動的に処理します。
Stripe で Webhook エンドポイントを {your-domain}/paykit/api/webhook に設定してください。
アプリでサブスクリプションの変更に対応するには、次のようにします。
export const paykit = createPayKit({
// ...
on: {
"customer.updated": ({ payload }) => {
// Sync subscription state to your app
await updateUserRole(payload.customerId, payload.subscriptions)
},
},
})
Stripe Customer Portal
顧客が請求情報を管理できるようにします(支払い方法の更新、請求書の確認、キャンセル)。
// Server-side
const { url } = await paykit.customerPortal({
customerId: "user_123",
returnUrl: "https://example.com/billing",
})
// Client-side
await paykitClient.customerPortal({
returnUrl: window.location.href,
})
コメント
Markdown 対応