金融与交易
在 Convex 应用中用…
当 Convex 应用需要 Stripe 结账、Webhook、订阅状态存储和服务端访问门控时,按此流程用 @convex-dev/stripe 接入。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Convex 应用中用 @conve特定
解决思路
- 安装组件:`npm install @convex-dev/stripe`。
- 创建 `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; ```
- 将 Stripe keys 存入 Convex env(使用 `env` micro power):`STRIPE_SECRET_KEY`(`sk_test_…` / `sk_live_…`)和 `STRIPE_WEBHOOK_SECRET`(`whsec_…`)。
- 创建 `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; ```
- 创建 `convex/billing.ts`,包含 checkout action 和 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'); }, }); ```
- 运行 `npx convex dev --once` —— 它会安装组件并推送函数。验证输出显示 `✔ Installed component stripe.`
- 在 Stripe Dashboard → Webhooks 中:添加 endpoint `https://<deployment>.convex.site/stripe/webhook`,订阅 `checkout.session.completed`、`customer.subscription.*`、`invoice.*`、`payment_intent.*`。将 signing secret 复制为 `STRIPE_WEBHOOK_SECRET`。
- 使用 `@convex-dev/stripe`(npm: `@convex-dev/stripe@^0.1.4`)—— 它通过 `registerRoutes` 内部处理 webhook 签名验证;不要写手动 `constructEvent` webhook。
- Stripe keys 放在 Convex env(使用 `env` micro power):`STRIPE_SECRET_KEY` 和 `STRIPE_WEBHOOK_SECRET`。
- 通过 `isSubscribed` query 基于服务端存储的订阅状态进行门控(读取组件表),不要基于客户端声明。
- `convex/convex.config.ts` 必须从 `@convex-dev/stripe/convex.config.js` 导入(不是 `.ts`)—— `.js` 扩展名是 Convex bundler 要求的。
适用边界 · 注意事项
- 不适用于非 Convex 应用。
- 不适用于不使用 Stripe 或不需要订阅计费的场景。
- 不适用于坚持手动处理 Stripe webhook 签名验证、而不使用 @convex-dev/stripe registerRoutes 的集成。