Agent Skills
› openclaw/clawhub
› convex-billing
convex-billing
GitHub为 Convex 应用集成 Stripe 支付功能,包含 Checkout、Webhook 处理及订阅状态管理。
Trigger Scenarios
需要添加付费或订阅功能
配置 Stripe Webhook 和密钥
Install
npx skills add openclaw/clawhub --skill convex-billing -g -y
SKILL.md
Frontmatter
{
"name": "convex-billing",
"description": "Add Stripe billing\/payments to the Convex app via @convex-dev\/stripe (checkout + webhook + gating)."
}
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.
Workflow
- Install the component:
npm install @convex-dev/stripe. - Create
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; - Store Stripe keys in Convex env (use the
envmicro power):STRIPE_SECRET_KEY(sk_test_… / sk_live_…) andSTRIPE_WEBHOOK_SECRET(whsec_…). - Create
convex/http.tsto register the webhook route (the component handles signature verification automatically):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; - Create
convex/billing.tswith a checkout action and a subscription-gate query: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"); }, }); - Run
npx convex dev --once— it will install the component and push the functions. Verify output shows✔ Installed component stripe. - In Stripe Dashboard → Webhooks: add endpoint
https://<deployment>.convex.site/stripe/webhook, subscribe tocheckout.session.completed,customer.subscription.*,invoice.*,payment_intent.*. Copy the signing secret asSTRIPE_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
envmicro 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.
Version History
- d6a8c68 Current 2026-08-20 02:43


