convex-billing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<!-- GENERATED from convex-agents content/capabilities/billing.json — do not edit by hand. -->
<!-- 由 convex-agents content/capabilities/billing.json 生成——请勿手动编辑。 -->

Add billing / payments

添加计费 / 支付

Wire Stripe to Convex using @convex-dev/stripe: a checkout action, an httpAction webhook registered by the component (signature-verified automatically), subscription state stored in the component's tables, and server-side gating via a query.
使用 @convex-dev/stripe 将 Stripe 接入 Convex:提供结账 action、由组件注册的 httpAction webhook(自动验证签名)、将订阅状态存储在组件的表中,并通过 query 实现服务端门控。

Workflow

工作流

  1. Install the component:
    npm install @convex-dev/stripe
    .
  2. Create
    convex/convex.config.ts
    :
    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. Store Stripe keys in Convex env (use the
    env
    micro power):
    STRIPE_SECRET_KEY
    (sk_test_… / sk_live_…) and
    STRIPE_WEBHOOK_SECRET
    (whsec_…).
  4. Create
    convex/http.ts
    to register the webhook route (the component handles signature verification automatically):
    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. Create
    convex/billing.ts
    with a checkout action and a subscription-gate query:
    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. Run
    npx convex dev --once
    — it will install the component and push the functions. Verify output shows
    ✔ Installed component stripe.
  7. In Stripe Dashboard → Webhooks: add endpoint
    https://<deployment>.convex.site/stripe/webhook
    , subscribe to
    checkout.session.completed
    ,
    customer.subscription.*
    ,
    invoice.*
    ,
    payment_intent.*
    . Copy the signing secret as
    STRIPE_WEBHOOK_SECRET
    .
  1. 安装组件:
    npm install @convex-dev/stripe
    。
  2. 创建
    convex/convex.config.ts
    :
    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
    micro power):
    STRIPE_SECRET_KEY
    (sk_test_… / sk_live_…)和
    STRIPE_WEBHOOK_SECRET
    (whsec_…)。
  4. 创建
    convex/http.ts
    注册 webhook 路由(组件会自动处理签名验证):
    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
    ,其中包含结账 action 和订阅门控 query:
    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
    。

Rules

规则

  • Use @convex-dev/stripe (npm: @convex-dev/stripe@^0.1.4) — it handles webhook signature verification internally via registerRoutes; do NOT write a manual constructEvent webhook.
  • Stripe keys live in Convex env (use the
    env
    micro power): STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET.
  • Gate on server-stored subscription state via isSubscribed query (reads component tables), not client claims.
  • convex/convex.config.ts must import from '@convex-dev/stripe/convex.config.js' (not .ts) — the .js extension is required by the Convex bundler.
  • 使用 @convex-dev/stripe(npm:@convex-dev/stripe@^0.1.4)——它通过 registerRoutes 在内部处理 webhook 签名验证;请勿编写手动 constructEvent webhook。
  • Stripe 密钥保存在 Convex env 中(使用
    env
    micro power):STRIPE_SECRET_KEY 和 STRIPE_WEBHOOK_SECRET。
  • 通过 isSubscribed query 对服务器中存储的订阅状态进行门控(读取组件表),而不是依赖客户端声明。
  • convex/convex.config.ts 必须从 '@convex-dev/stripe/convex.config.js' 导入(而不是 .ts)——Convex bundler 要求使用 .js 扩展名。