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(自动验证签名)、存储在组件表中的订阅状态,以及通过查询实现的服务器端访问控制。
Workflow
工作流程
- Install the component: .
npm install @convex-dev/stripe - Create :
convex/convex.config.tstsimport { defineApp } from 'convex/server'; import stripe from '@convex-dev/stripe/convex.config.js'; const app = defineApp(); app.use(stripe); export default app; - Store Stripe keys in Convex env (use the micro power):
env(sk_test_… / sk_live_…) andSTRIPE_SECRET_KEY(whsec_…).STRIPE_WEBHOOK_SECRET - Create to register the webhook route (the component handles signature verification automatically):
convex/http.tstsimport { 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; - Create with a checkout action and a subscription-gate query:
convex/billing.tstsimport { 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'); }, }); - Run — it will install the component and push the functions. Verify output shows
npx convex dev --once✔ Installed component stripe. - In Stripe Dashboard → Webhooks: add endpoint , subscribe to
https://<deployment>.convex.site/stripe/webhook,checkout.session.completed,customer.subscription.*,invoice.*. Copy the signing secret aspayment_intent.*.STRIPE_WEBHOOK_SECRET
- 安装组件:。
npm install @convex-dev/stripe - 创建:
convex/convex.config.tstsimport { defineApp } from 'convex/server'; import stripe from '@convex-dev/stripe/convex.config.js'; const app = defineApp(); app.use(stripe); export default app; - 将Stripe密钥存储在Convex环境变量中(使用微功能):
env(sk_test_… / sk_live_…)和STRIPE_SECRET_KEY(whsec_…)。STRIPE_WEBHOOK_SECRET - 创建以注册Webhook路由(组件会自动处理签名验证):
convex/http.tstsimport { 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; - 创建,包含结账action和订阅访问控制查询:
convex/billing.tstsimport { 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'); }, }); - 运行— 该命令会安装组件并推送函数。验证输出显示
npx convex dev --once✔ Installed component stripe. - 在Stripe控制台 → 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 micro power): STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET.
env - 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环境变量中(使用微功能):STRIPE_SECRET_KEY和STRIPE_WEBHOOK_SECRET。
env - 通过isSubscribed查询(读取组件表)基于服务器存储的订阅状态进行访问控制,而非客户端声明。
- convex/convex.config.ts必须从'@convex-dev/stripe/convex.config.js'导入(而非.ts)—— Convex打包器要求必须使用.js扩展名。