# Stripe Checkout, subscriptions and billing - Package: @bijectionhq/stripe - Category: Payments - Status: Included - License: Apache-2.0 - Bijection source (repository access required): https://github.com/bijectionhq/stripe/tree/22e23e2bdfcc88b48a1604a80bf60d8f15494360 - Upstream: https://github.com/convex/stripe/tree/5681a698cb59d96e87a2dc219904fc848b90ec9d - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/stripe ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/stripe.git cd stripe git checkout 22e23e2bdfcc88b48a1604a80bf60d8f15494360 ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This source is included in Bijection's workspace. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @convex-dev/stripe; check the Bijection adaptation separately. A Convex component that handles the complete Stripe integration lifecycle including checkout sessions, subscription management, customer billing portals, and webhook synchronization. It automatically creates and manages customers, processes payments and subscriptions, and keeps Stripe data synchronized with your Convex database through webhook handlers. The component provides both a StripeSubscriptions client API and direct database queries for real-time payment data. ### Use cases • **SaaS subscription billing** - Implement recurring billing with seat-based pricing, subscription upgrades/downgrades, and automatic customer portal access for plan management • **One-time payment processing** - Handle product purchases, service fees, or donations with Stripe Checkout integration and payment status tracking • **Team billing management** - Link subscriptions to organizations with quantity-based pricing, allowing teams to add or remove seats dynamically • **Customer self-service billing** - Generate Customer Portal sessions so users can update payment methods, download invoices, and manage their billing without support • **Multi-tenant payment tracking** - Query payments, subscriptions, and invoices in real-time across users and organizations with automatic webhook synchronization ### How it works The component uses a StripeSubscriptions client that wraps Stripe API calls within Convex actions, handling customer creation, checkout sessions, and subscription management. You configure it by adding the component to your convex.config.ts and setting up webhook routes in http.ts that point to the component's webhook handler at /stripe/webhook. Webhook events automatically synchronize Stripe data to dedicated Convex tables (customers, subscriptions, payments, invoices) in the component's namespace. The component handles 10+ webhook events including checkout.session.completed, customer.subscription.updated, and payment_intent.succeeded, maintaining real-time data consistency. You access payment data through the component's public queries like listSubscriptionsByUserId and getPayment, or use the client methods like createCheckoutSession and updateSubscriptionQuantity in your actions. The component supports linking payments to both individual users and organizations through metadata, enabling flexible billing models. ### Upstream README # @convex-dev/stripe A Convex component for integrating Stripe payments, subscriptions, and billing into your Convex application. Requires Node.js 18 or later, matching the Stripe Node SDK v22 runtime requirement. ## Features - 🛒 **Checkout Sessions** - Create one-time payment and subscription checkouts - 📦 **Subscription Management** - Create, update, cancel subscriptions - 👥 **Customer Management** - Automatic customer creation and linking - 💳 **Customer Portal** - Let users manage their billing - 🪑 **Seat-Based Pricing** - Update subscription quantities for team billing - 🔗 **User/Org Linking** - Link payments and subscriptions to users or organizations - 🔔 **Webhook Handling** - Automatic sync of Stripe data to your Convex database - 📊 **Real-time Data** - Query payments, subscriptions, invoices in real-time ## Quick Start ### 1. Install the Component ```bash npm install @convex-dev/stripe ``` ### 2. Add to Your Convex App Create or update `convex/convex.config.ts`: ```typescript import { defineApp } from "convex/server"; import stripe from "@convex-dev/stripe/convex.config.js"; const app = defineApp(); app.use(stripe); export default app; ``` ### 3. Set Up Environment Variables Add these to your [Convex Dashboard](https://dashboard.convex.dev) → Settings → Environment Variables: | Variable | Description | | ----------------------- | ------------------------------------------------------- | | `STRIPE_SECRET_KEY` | Your Stripe secret key (`sk_test_...` or `sk_live_...`) | | `STRIPE_WEBHOOK_SECRET` | Webhook signing secret (`whsec_...`) - see Step 4 | ### 4. Configure Stripe Webhooks 1. Go to [Stripe Dashboard → Developers → Webhooks](https://dashboard.stripe.com/test/webhooks) 2. Click **"Add endpoint"** 3. Enter your webhook URL: ``` https://.convex.site/stripe/webhook ``` (Find your deployment name in the Convex dashboard - it's the part before `.convex.cloud` in your URL) 4. Select these events: - `checkout.session.completed` - `customer.created` - `customer.updated` - `customer.deleted` - `customer.subscription.created` - `customer.subscription.updated` - `customer.subscription.deleted` - `invoice.created` - `invoice.finalized` - `invoice.updated` - `invoice.paid` - `invoice.payment_failed` - `payment_intent.succeeded` - `payment_intent.payment_failed` 5. Click **"Add endpoint"** 6. Copy the **Signing secret** and add it as `STRIPE_WEBHOOK_SECRET` in Convex ### 5. Register Webhook Routes Create `convex/http.ts`: ```typescript import { httpRouter } from "convex/server"; import { components } from "./_generated/api"; import { registerRoutes } from "@convex-dev/stripe"; const http = httpRouter(); // Register Stripe webhook handler at /stripe/webhook registerRoutes(http, components.stripe, { webhookPath: "/stripe/webhook", apiVersion: "2026-04-22.dahlia", // Optional }); export default http; ``` ### 6. Use the Component Create `convex/stripe.ts`: ```typescript import { action } 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, {}); // Create a checkout session for a subscription 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"); // Get or create a Stripe customer const customer = await stripeClient.getOrCreateCustomer(ctx, { userId: identity.subject, email: identity.email, name: identity.name, }); // Create checkout session return await stripeClient.createCheckoutSession(ctx, { priceId: args.priceId, customerId: customer.customerId, mode: "subscription", successUrl: "http://localhost:5173/?success=true", cancelUrl: "http://localhost:5173/?canceled=true", subscriptionMetadata: { userId: identity.subject }, }); }, }); // Create a checkout session for a one-time payment export const createPaymentCheckout = 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: "payment", successUrl: "http://localhost:5173/?success=true", cancelUrl: "http://localhost:5173/?canceled=true", paymentIntentMetadata: { userId: identity.subject }, }); }, }); ``` ## API Reference ### StripeSubscriptions Client ```typescript import { StripeSubscriptions } from "@convex-dev/stripe"; const stripeClient = new StripeSubscriptions(components.stripe, { STRIPE_SECRET_KEY: "sk_...", // Optional, defaults to process.env.STRIPE_SECRET_KEY apiVersion: "2026-04-22.dahlia", // Optional Stripe API version }); ``` #### Methods | Method | Description | |--------|-------------| | `createCheckoutSession()` | Create a Stripe Checkout session | | `createCustomerPortalSession()` | Generate a Customer Portal URL | | `createCustomer()` | Create a new Stripe customer | | `getOrCreateCustomer()` | Get existing or create new customer | | `cancelSubscription()` | Cancel a subscription | | `reactivateSubscription()` | Reactivate a subscription set to cancel | | `updateSubscriptionQuantity()` | Update seat count | ### createCheckoutSession ```typescript await stripeClient.createCheckoutSession(ctx, { priceId: "price_...", customerId: "cus_...", // Optional mode: "subscription", // "subscription" | "payment" | "setup" successUrl: "https://...", cancelUrl: "https://...", quantity: 1, // Optional, default 1 metadata: {}, // Optional, session metadata subscriptionMetadata: {}, // Optional, attached to subscription paymentIntentMetadata: {}, // Optional, attached to payment intent params: { allow_promotion_codes: true, ui_mode: "embedded_page", return_url: "https://...", }, // Optional Stripe Checkout Session params }); ``` `params` is a typed passthrough for Stripe Checkout Session fields that the component does not model directly. Fields in `params` override constructed defaults, except `mode`, which remains controlled by the top-level `mode` argument. For non-hosted Checkout UI modes, the component omits `successUrl` and `cancelUrl` from the Stripe request and expects redirect behavior to be supplied through Stripe-supported params such as `return_url`. ### Component Queries Access data directly via the component's public queries: ```typescript import { query } from "./_generated/server"; import { components } from "./_generated/api"; // List subscriptions for a user export const getUserSubscriptions = query({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) return []; return await ctx.runQuery( components.stripe.public.listSubscriptionsByUserId, { userId: identity.subject }, ); }, }); // List payments for a user export const getUserPayments = query({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) return []; return await ctx.runQuery(components.stripe.public.listPaymentsByUserId, { userId: identity.subject, }); }, }); ``` ### Available Public Queries | Query | Arguments | Description | | --------------------------- | ----------------------- | --------------------------------- | | `getCustomer` | `stripeCustomerId` | Get a customer by Stripe ID | | `listSubscriptions` | `stripeCustomerId` | List subscriptions for a customer | | `listSubscriptionsByUserId` | `userId` | List subscriptions for a user | | `getSubscription` | `stripeSubscriptionId` | Get a subscription by ID | | `getSubscriptionByOrgId` | `orgId` | Get subscription for an org | | `getPayment` | `stripePaymentIntentId` | Get a payment by ID | | `listPayments` | `stripeCustomerId` | List payments for a customer | | `listPaymentsByUserId` | `userId` | List payments for a user | | `listPaymentsByOrgId` | `orgId` | List payments for an org | | `listInvoices` | `stripeCustomerId` | List invoices for a customer | | `listInvoicesByUserId` | `userId` | List invoices for a user | | `listInvoicesByOrgId` | `orgId` | List invoices for an org | ## Webhook Events The component automatically handles these Stripe webhook events: | Event | Action | | ------------------------------- | ----------------------------------- | | `customer.created` | Creates customer record | | `customer.updated` | Updates customer record | | `customer.deleted` | Scrubs customer PII | | `customer.subscription.created` | Creates subscription record | | `customer.subscription.updated` | Updates subscription record | | `customer.subscription.deleted` | Marks subscription as canceled | | `payment_intent.succeeded` | Creates payment record | | `payment_intent.payment_failed` | Updates payment status | | `invoice.created` | Creates invoice record | | `invoice.finalized` | Upserts invoice record | | `invoice.updated` | Mirrors invoice metadata changes | | `invoice.paid` | Upserts invoice as paid | | `invoice.payment_failed` | Marks invoice as failed | | `checkout.session.completed` | Handles completed checkout sessions | ### Custom Webhook Handlers Add custom logic to webhook events: ```typescript import { httpRouter } from "convex/server"; import { components } from "./_generated/api"; import { registerRoutes } from "@convex-dev/stripe"; import type Stripe from "stripe"; const http = httpRouter(); registerRoutes(http, components.stripe, { events: { "customer.subscription.updated": async (ctx, event: Stripe.CustomerSubscriptionUpdatedEvent) => { const subscription = event.data.object; console.log("Subscription updated:", subscription.id, subscription.status); // Add custom logic here }, }, onEvent: async (ctx, event: Stripe.Event) => { // Called for ALL events - useful for logging/analytics console.log("Stripe event:", event.type); }, }); export default http; ``` ## Database Schema The component creates these tables in its namespace: ### customers | Field | Type | Description | | ------------------ | ------- | ------------------ | | `stripeCustomerId` | string | Stripe customer ID | | `email` | string? | Customer email | | `name` | string? | Customer name | | `metadata` | object? | Custom metadata | ### subscriptions | Field | Type | Description | | ---------------------- | ------- | ------------------------- | | `stripeSubscriptionId` | string | Stripe subscription ID | | `stripeCustomerId` | string | Customer ID | | `status` | string | Subscription status | | `priceId` | string | Price ID | | `quantity` | number? | Seat count | | `currentPeriodEnd` | number | Period end timestamp | | `cancelAtPeriodEnd` | boolean | Will cancel at period end | | `userId` | string? | Linked user ID | | `orgId` | string? | Linked org ID | | `metadata` | object? | Custom metadata | ### checkout_sessions | Field | Type | Description | | ------------------------- | ------- | ----------------------------------------- | | `stripeCheckoutSessionId` | string | Checkout session ID | | `stripeCustomerId` | string? | Customer ID | | `status` | string | Session status | | `mode` | string | Session mode (payment/subscription/setup) | | `metadata` | object? | Custom metadata | ### payments | Field | Type | Description | | ----------------------- | ------- | ----------------- | | `stripePaymentIntentId` | string | Payment intent ID | | `stripeCustomerId` | string? | Customer ID | | `amount` | number | Amount in cents | | `currency` | string | Currency code | | `status` | string | Payment status | | `created` | number | Created timestamp | | `userId` | string? | Linked user ID | | `orgId` | string? | Linked org ID | | `metadata` | object? | Custom metadata | ### invoices | Field | Type | Description | | ---------------------- | ------- | ----------------- | | `stripeInvoiceId` | string | Invoice ID | | `stripeCustomerId` | string | Customer ID | | `stripeSubscriptionId` | string? | Subscription ID | | `status` | string | Invoice status | | `amountDue` | number | Amount due | | `amountPaid` | number | Amount paid | | `created` | number | Created timestamp | | `userId` | string? | Linked user ID | | `orgId` | string? | Linked org ID | | `metadata` | object? | Invoice metadata | ## Example App Check out the full example app in the [`example/`](./example) directory: ```bash git clone https://github.com/get-convex/convex-stripe cd convex-stripe npm install npm run dev ``` The example includes: - Landing page with product showcase - One-time payments and subscriptions - User profile with order history - Subscription management (cancel, update seats) - Customer portal integration - Team/organization billing ## Authentication This component works with any Convex authentication provider. The example uses [Clerk](https://clerk.com): 1. Create a Clerk application at [clerk.com](https://clerk.com) 2. Add `VITE_CLERK_PUBLISHABLE_KEY` to your `.env.local` 3. Create `convex/auth.config.ts`: ```typescript export default { providers: [ { domain: "https://your-clerk-domain.clerk.accounts.dev", applicationID: "convex", }, ], }; ``` ## Troubleshooting ### Tables are empty after checkout Make sure you've: 1. Set `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` in Convex environment variables 2. Configured the webhook endpoint in Stripe with the correct events 3. Added `invoice.created` and `invoice.finalized` events (not just `invoice.paid`) ### "Not authenticated" errors Ensure your auth provider is configured: 1. Create `convex/auth.config.ts` with your provider 2. Run `npx convex dev` to push the config 3. Verify the user is signed in before calling actions ### Webhooks returning 400/500 Check the Convex logs in your dashboard for errors. Common issues: - Missing `STRIPE_WEBHOOK_SECRET` - Wrong webhook URL (should be `https://.convex.site/stripe/webhook`) - Missing events in webhook configuration ## License Apache-2.0