mcpField
C
convex-billing

convex-billing

github.com/convex開発6936.9k 回インストール1 日前に更新
リポジトリ

convex-billing について

convex-billingは、@convex-dev/stripeを使ってConvexにStripeの課金機能を追加するスキルです。チェックアウト、署名検証付きWebhook、コンポーネントに保存するサブスクリプション状態、サーバー側のアクセス制御を設定します。

主な機能

  1. 01@convex-dev/stripeの導入と設定
  2. 02チェックアウトactionの設定
  3. 03署名検証付きWebhookの登録
  4. 04コンポーネントのテーブルへの状態保存
  5. 05サーバー側クエリによるアクセス制御

活用シーン

  1. 01ConvexアプリへのStripeサブスクリプション決済の追加
  2. 02Stripeのサブスクリプションや決済イベントの処理
  3. 03保存された契約状態に応じた機能制限

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/convex/agent-skills --skill convex-billing

インストール後、Claude Code で/convex-billingを入力して読み込まれたか確認します。

詳細

請求と決済を追加する

@convex-dev/stripe を使って Stripe を Convex に接続します。チェックアウトアクション、コンポーネントによって登録される httpAction Webhook(署名は自動的に検証されます)、コンポーネントのテーブルに保存されるサブスクリプションの状態、クエリによるサーバー側のアクセス制御を設定します。

ワークフロー

  1. コンポーネントをインストールします:npm install @convex-dev/stripe。
  2. convex/convex.config.ts を作成します:
    import { defineApp } from 'convex/server';
    import stripe from '@convex-dev/stripe/convex.config.js';
    const app = defineApp();
    app.use(stripe);
    export default app;
    
  3. Stripe キーを Convex の env に保存します(env マイクロパワーを使用します):STRIPE_SECRET_KEY(sk_test_… / sk_live_…)と STRIPE_WEBHOOK_SECRET(whsec_…)。
  4. Webhook ルートを登録するために convex/http.ts を作成します(署名検証はコンポーネントが自動的に処理します):
    import { httpRouter } from 'convex/server';
    import { components } from './_generated/api';
    import { registerRoutes } from '@convex-dev/stripe';
    const http = httpRouter();
    registerRoutes(http, components.stripe, { webhookPath: '/stripe/webhook' });
    export default http;
    
  5. チェックアウトアクションとサブスクリプションを確認するクエリを含む convex/billing.ts を作成します:
    import { action, query } from './_generated/server';
    import { components } from './_generated/api';
    import { StripeSubscriptions } from '@convex-dev/stripe';
    import { v } from 'convex/values';
    const stripeClient = new StripeSubscriptions(components.stripe, {});
    export const createSubscriptionCheckout = action({
      args: { priceId: v.string() },
      returns: v.object({ sessionId: v.string(), url: v.union(v.string(), v.null()) }),
      handler: async (ctx, args) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error('Not authenticated');
        const customer = await stripeClient.getOrCreateCustomer(ctx, { userId: identity.subject, email: identity.email, name: identity.name });
        return await stripeClient.createCheckoutSession(ctx, { priceId: args.priceId, customerId: customer.customerId, mode: 'subscription', successUrl: `${process.env.SITE_URL ?? 'http://localhost:3000'}/?success=true`, cancelUrl: `${process.env.SITE_URL ?? 'http://localhost:3000'}/?canceled=true`, subscriptionMetadata: { userId: identity.subject } });
      },
    });
    export const isSubscribed = query({
      args: {},
      returns: v.boolean(),
      handler: async (ctx) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) return false;
        const subscriptions = await ctx.runQuery(components.stripe.public.listSubscriptionsByUserId, { userId: identity.subject });
        return subscriptions.some((sub) => sub.status === 'active' || sub.status === 'trialing');
      },
    });
    
  6. npx convex dev --once を実行します。コンポーネントがインストールされ、関数がプッシュされます。出力に ✔ Installed component stripe. と表示されることを確認します。
  7. Stripe Dashboard → Webhooks で、エンドポイント https://<deployment>.convex.site/stripe/webhook を追加し、checkout.session.completed、customer.subscription.*、invoice.*、payment_intent.* を登録します。署名シークレットをコピーし、STRIPE_WEBHOOK_SECRET に設定します。

ルール

  • @convex-dev/stripe(npm: @convex-dev/stripe@^0.1.4)を使用してください。registerRoutes を介して Webhook の署名検証を内部で処理するため、独自の constructEvent Webhook は作成しないでください。
  • Stripe キーは Convex の env に保存します(env マイクロパワーを使用します):STRIPE_SECRET_KEY と STRIPE_WEBHOOK_SECRET。
  • クライアント側の申告ではなく、サーバーに保存されたサブスクリプションの状態を isSubscribed クエリ(コンポーネントのテーブルを読み取ります)で確認し、アクセスを制御します。
  • convex/convex.config.ts では '@convex-dev/stripe/convex.config.js' からインポートする必要があります(.ts ではありません)。Convex バンドラーで .js 拡張子が必要です。

コメント