{"id":31160075,"url":"https://github.com/raideno/convex-stripe","last_synced_at":"2026-02-26T00:08:32.202Z","repository":{"id":312415739,"uuid":"1047420132","full_name":"raideno/convex-stripe","owner":"raideno","description":"Easy stripe integration for convex.","archived":false,"fork":false,"pushed_at":"2025-09-16T15:10:36.000Z","size":702,"stargazers_count":7,"open_issues_count":7,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-16T17:55:15.047Z","etag":null,"topics":["billing","convex","payment","stripe"],"latest_commit_sha":null,"homepage":"https://convex-billing-demo.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/raideno.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/funding.yaml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"raideno","buy_me_a_coffee":"raideno"}},"created_at":"2025-08-30T11:35:24.000Z","updated_at":"2025-09-15T20:54:48.000Z","dependencies_parsed_at":"2025-08-30T13:51:38.772Z","dependency_job_id":"3ac8144a-6adc-4858-86f4-4a9d57b454c7","html_url":"https://github.com/raideno/convex-stripe","commit_stats":null,"previous_names":["raideno/convex-billing","raideno/convex-stripe"],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/raideno/convex-stripe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raideno%2Fconvex-stripe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raideno%2Fconvex-stripe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raideno%2Fconvex-stripe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raideno%2Fconvex-stripe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raideno","download_url":"https://codeload.github.com/raideno/convex-stripe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raideno%2Fconvex-stripe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275867745,"owners_count":25542807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-19T02:00:09.700Z","response_time":108,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["billing","convex","payment","stripe"],"created_at":"2025-09-19T02:07:22.691Z","updated_at":"2026-02-26T00:08:32.197Z","avatar_url":"https://github.com/raideno.png","language":"TypeScript","funding_links":["https://github.com/sponsors/raideno","https://buymeacoffee.com/raideno"],"categories":[],"sub_categories":[],"readme":"# Convex Stripe\n\nStripe [syncing](./references/tables.md), subscriptions, [checkouts](#-checkout-action) and stripe connect for Convex apps. Implemented according to the best practices listed in [Theo's Stripe Recommendations](https://github.com/t3dotgg/stripe-recommendations).\n\n## Installation\n\n```sh [npm]\nnpm install @raideno/convex-stripe stripe\n```\n\n## Configuration\n\n### 1. Set up Stripe  \n- Create a Stripe account.  \n- Configure a webhook pointing to:  \n  ```\n  https://\u003cyour-convex-app\u003e.convex.site/stripe/webhook\n  ```\n- Enable the following [Stripe Events](./references/events.md).  \n- Enable the [Stripe Billing Portal](https://dashboard.stripe.com/test/settings/billing/portal).\n\n### 2. Set Environment Variables on Convex\n\n```bash\nnpx convex env set STRIPE_SECRET_KEY \"\u003csecret\u003e\"\nnpx convex env set STRIPE_ACCOUNT_WEBHOOK_SECRET \"\u003csecret\u003e\"\n```\n\n### 3. Add tables.\n\nCheck [Tables Schemas](./references/tables.md) to know more about the synced tables.\n\n```ts [convex/schema.ts]\nimport { defineSchema } from \"convex/server\";\nimport { stripeTables } from \"@raideno/convex-stripe/server\";\n\nexport default defineSchema({\n  ...stripeTables,\n  // your other tables...\n});\n```\n\n### 4. Initialize the library\n\n```ts [convex/stripe.ts]\nimport { internalConvexStripe } from \"@raideno/convex-stripe/server\";\n\nexport const { stripe, store, sync } = internalConvexStripe({\n  stripe: {\n    secret_key: process.env.STRIPE_SECRET_KEY!,\n    account_webhook_secret: process.env.STRIPE_ACCOUNT_WEBHOOK_SECRET!,\n  },\n});\n\nexport const createCustomer = internalAction({\n  args: {\n    email: v.optional(v.string()),\n    entityId: v.string(),\n  },\n  handler: async (context, args) =\u003e {\n    return stripe.customers.create(context, {\n      email: args.email,\n      entityId: args.entityId,\n    });\n  },\n});\n\n```\n\n\u003e **Note:** All exposed actions (store, sync, createEntity) are **internal**. Meaning they can only be called from other convex functions, you can wrap them in public actions when needed.  \n\u003e **Important:** `store` must always be exported, as it is used internally.\n\n### 5. Register HTTP routes\n\n```ts [convex/http.ts]\nimport { httpRouter } from \"convex/server\";\nimport { stripe } from \"./stripe\";\n\nconst http = httpRouter();\n\n// registers POST /stripe/webhook\n// registers GET /stripe/return/*\nstripe.addHttpRoutes(http);\n\nexport default http;\n```\n\n### 6. Stripe customers\n\nIdeally you want to create a stripe customer the moment a new entity (user, organization, etc) is created.\n\nAn `entityId` refers to something you are billing. It can be a user, organization or any other thing. With each entity must be associated a stripe customer and the stripe customer can be created using the [`createEntity` action](#createentity-action).\n\nBelow are with different auth providers examples where the user is the entity we are billing.\n\n::: code-group\n\n```ts [convex-auth]\n\"convex/auth.ts\"\n\n// example with convex-auth: https://labs.convex.dev/auth\n\nimport { convexAuth } from \"@convex-dev/auth/server\";\nimport { Password } from \"@convex-dev/auth/providers/Password\";\nimport { internal } from \"./_generated/api\";\n\nexport const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({\n  providers: [Password],\n  callbacks: {\n    afterUserCreatedOrUpdated: async (context, args) =\u003e {\n      await context.scheduler.runAfter(0, internal.stripe.createCustomer, {\n        /*\n         * will call stripe.customers.create\n         */\n        entityId: args.userId,\n        email: args.profile.email,\n      });\n    },\n  },\n});\n```\n\n```ts [better-auth]\n\"convex/auth.ts\"\n\n// example with better-auth: https://convex-better-auth.netlify.app/\n\n// coming soon...\n```\n\n```ts [clerk]\n\"convex/auth.ts\"\n\n// example with clerk: https://docs.convex.dev/auth/clerk\n\n// coming soon...\n```\n\n:::\n\n### 7. Run `sync` action\n\nIn your convex project's dashboard. Go the **Functions** section and execute the `sync` action.\n\nThis is done to sync already existing stripe data into your convex database.\nIt must be done in both your development and production deployments after installing or updating the library.\n\nThis might not be necessary if you are starting with a fresh empty stripe project.\n\n### 8. Start building \nNow you can use the provided functions to:\n- Generate a subscription or payment link [`stripe.subscribe`](#subscribe-function), [`stripe.pay`](#pay-function) for a given entity.\n- Generate a link to the entity's [`stripe.portal`](#portal-function) to manage their subscriptions.\n- Create stripe connect accounts and link them, [`stripe.accounts.create`](#), [`stripe.accounts.link`](#).\n- Consult the [synced tables](./references/tables.md).\n- Etc.\n\n\n## Usage\n\nThe library automatically syncs the [following tables](./references/tables.md).\n\nYou can query these tables at any time to:\n\n- List available products/plans and prices.\n- Retrieve customers and their `customerId`.\n- Check active subscriptions.\n- Etc.\n\n\n### `customers.create` Function\n\nCreates or updates a Stripe customer for a given entity (user or organization). Will call [`stripe.customers.create`](https://docs.stripe.com/api/customers/create) under the hood.\n\nThis should be called whenever a new entity is created in your app, or when you want to ensure the entity has a Stripe customer associated with it.\n\n```ts\nimport { v } from \"convex/values\";\nimport { stripe } from \"./stripe\";\nimport { action, internal } from \"./_generated/api\";\n\nexport const createCustomer = internalAction({\n  args: {\n    email: v.optional(v.string()),\n    entityId: v.string(),\n  },\n  handler: async (context, args) =\u003e {\n    return stripe.customers.create(context, {\n      email: args.email,\n      entityId: args.entityId,\n    });\n  },\n});\n```\n\n**Notes:**\n\n- `entityId` is your app’s internal ID (user/org).\n- `customerId` is stripe's internal ID.\n- `email` is optional, but recommended so the Stripe customer has a contact email.\n- If the entity already has a Stripe customer, `createEntity` will return the existing one instead of creating a duplicate.\n- Typically, you’ll call this automatically in your user/org creation flow (see [Configuration - 6](#configuration)).\n\n\n### `sync` Action\n\nSynchronizes Stripe resources with your Convex database.\n\nThis action is typically manually called or setup to be automatically called in your ci/cd pipeline.\n\n**Parameters:**\n\n- `data` (optional, default: `true`): Syncs all existing Stripe resources to Convex tables.\n- `data.withConnect` (option, default: `false`): Syncs all existing Stripe resources from linked accounts to Convex tables.\n- `webhook.account` (optional, default: `false`): Creates/updates the account webhook endpoint. Returns the webhook secret if a new endpoint is created. You must set it in your convex environment variables as `STRIPE_ACCOUNT_WEBHOOK_SECRET`.\n- `webhook.connect` (optional, default: `false`): Creates/updates the connect webhook endpoint. Returns the webhook secret if a new endpoint is created. You must set it in your convex environment variables as `STRIPE_CONNECT_WEBHOOK_SECRET`.\n- `portal` (optional, default: `false`): Creates the default billing portal configuration if it doesn't exist.\n- `unstable_catalog` (optional, default: `false`): Creates the default provided products and prices passed in the configuration.\n\n### `subscribe` Function\n\nCreates a Stripe Subscription Checkout session for a given entity. Will call [`stripe.checkout.sessions.create`](https://docs.stripe.com/api/checkout/sessions/create) under the hood, the same parameters can be passed.\n\n```ts\nimport { v } from \"convex/values\";\n\nimport { stripe } from \"./stripe\";\nimport { action, internal } from \"./_generated/api\";\n\nexport const createCheckout = action({\n  args: { entityId: v.string(), priceId: v.string() },\n  handler: async (context, args) =\u003e {\n    // TODO: add your own auth/authorization logic here\n\n    const response = await stripe.subscribe(context, {\n      entityId: args.entityId,\n      priceId: args.priceId,\n      mode: \"subscription\",\n      success_url: \"http://localhost:3000/payments/success\",\n      cancel_url: \"http://localhost:3000/payments/cancel\",\n      /*\n       * Other parameters from stripe.checkout.sessions.create(...)\n       */\n    }, {\n      /*\n       * Optional Stripe Request Options\n       */\n    });\n\n    return response.url;\n  },\n});\n```\n\n\n### `portal` Function\n\nAllows an entity to manage their subscription via the Stripe Portal. Will call [`stripe.billingPortal.sessions.create`](https://docs.stripe.com/api/customer_portal/sessions/create) under the hood, the same parameters can be passed.\n\n```ts\nimport { v } from \"convex/values\";\n\nimport { stripe } from \"./stripe\";\nimport { action, internal } from \"./_generated/api\";\n\nexport const portal = action({\n  args: { entityId: v.string() },\n  handler: async (context, args) =\u003e {\n    const response = await stripe.portal(context, {\n      entityId: args.entityId,\n      returnUrl: \"http://localhost:3000/return-from-portal\",\n      /*\n       * Other parameters from stripe.billingPortal.sessions.create(...)\n       */\n    }, {\n      /*\n       * Optional Stripe Request Options\n       */\n    });\n\n    return response.url;\n  },\n});\n```\nThe provided entityId must have a customerId associated to it otherwise the action will throw an error.\n\n\n### `pay` Function\n\nCreates a Stripe One Time Payment Checkout session for a given entity. Will call [`stripe.checkout.sessions.create`](https://docs.stripe.com/api/checkout/sessions/create) under the hood, the same parameters can be passed.\n\n```ts\nimport { v } from \"convex/values\";\n\nimport { stripe } from \"./stripe\";\nimport { action, internal } from \"./_generated/api\";\n\nexport const pay = action({\n  args: { entityId: v.string(), orderId: v.string(), priceId: v.string() },\n  handler: async (context, args) =\u003e {\n    // Add your own auth/authorization logic here\n\n    const response = await stripe.pay(context, {\n      referenceId: args.orderId,\n      entityId: args.entityId,\n      mode: \"payment\",\n      line_items: [{ price: args.priceId, quantity: 1 }],\n      success_url: `${process.env.SITE_URL}/?return-from-pay=success`,\n      cancel_url: `${process.env.SITE_URL}/?return-from-pay=cancel`,\n      /*\n       * Other parameters from stripe.checkout.sessions.create(...)\n       */\n    }, {\n      /*\n       * Optional Stripe Request Options\n       */\n    });\n\n    return response.url;\n  },\n});\n```\n\n\n## Best Practices\n\n- Always create a Stripe customer (`createEntity`) when a new entity is created.  \n- Use `metadata` or `marketing_features` on products to store feature flags or limits.  \n- Run `sync` when you first configure the extension to sync already existing stripe resources.  \n- Never expose internal actions directly to clients, wrap them in public actions with proper authorization.\n\n\n## Resources\n\n- [Convex Documentation](https://docs.convex.dev)  \n- [Stripe Documentation](https://stripe.com/docs)  \n- [GitHub Repository](https://github.com/raideno/convex-stripe)\n- [Theo's Stripe Recommendations](https://github.com/t3dotgg/stripe-recommendations)\n\n\n## Development\n\nClone the repository:\n\n```bash\ngit clone git@github.com:raideno/convex-stripe.git\ncd convex-stripe\n```\n\nInstall the dependencies:\n\n```bash\nnpm install\n```\n\nStart the development server:\n\n```bash\n# automatically rebuild lib on changes\nnpm run dev --workspace @raideno/convex-stripe\n# run the demo app\nnpm run dev --workspace demo\n```\n\n## Contributions\n\nAll contributions are welcome! Please open an issue or a PR.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraideno%2Fconvex-stripe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraideno%2Fconvex-stripe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraideno%2Fconvex-stripe/lists"}