### Install Dependencies and Run Locally Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md Installs project dependencies and starts the local development server. ```sh npm i npm run dev ``` -------------------------------- ### Build One-Off Package Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md Cleans the project, installs dependencies, and creates a package for distribution. ```sh npm run clean npm ci npm pack ``` -------------------------------- ### CheckoutLink with Free Trial Configuration Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md Configures a free trial for a product by specifying `trialInterval` and `trialIntervalCount`. This example sets up a 7-day free trial. ```typescript Start 7-Day Free Trial ``` -------------------------------- ### Install Convex Polar Source: https://github.com/get-convex/polar/blob/main/_autodocs/README.md Install the Convex Polar package using npm. ```bash npm install @convex-dev/polar ``` -------------------------------- ### Basic Convex App Setup with Polar Source: https://github.com/get-convex/polar/blob/main/_autodocs/README.md Configure your Convex application to use the Polar component by importing and using it in your `convex.config.ts` file. ```typescript // convex/convex.config.ts import { defineApp } from "convex/server"; import polar from "@convex-dev/polar/convex.config.js"; const app = defineApp(); app.use(polar); export default app; ``` -------------------------------- ### Example Product IDs Configuration Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Define product IDs for different pricing tiers. Ensure these IDs match the ones created in your Polar account. ```typescript products: { starter: "prod_starter_monthly", pro: "prod_pro_monthly", proYearly: "prod_pro_yearly", enterprise: "prod_enterprise", } ``` -------------------------------- ### Run listUserSubscriptions Query Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Example of how to retrieve a list of all active subscriptions for a user. This query returns an array of subscription objects, each potentially including product information. ```typescript const subscriptions = await ctx.runQuery(api.lib.listUserSubscriptions, { userId: user._id }); ``` -------------------------------- ### Install Polar Dependencies Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Install the necessary Polar packages for your Convex project using npm. ```bash npm install @convex-dev/polar @polar-sh/sdk @polar-sh/checkout ``` -------------------------------- ### Run getCustomerByUserId Query Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Example of how to execute the `getCustomerByUserId` query. This requires the `userId` of the user whose customer record is to be fetched. ```typescript const customer = await ctx.runQuery(api.lib.getCustomerByUserId, { userId: user._id }); ``` -------------------------------- ### Run getSubscription Query Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Example of calling the `getSubscription` query with a specific subscription ID. This is useful for retrieving details of a single subscription. ```typescript const subscription = await ctx.runQuery(api.lib.getSubscription, { id: "sub_123" }); ``` -------------------------------- ### Convex Backend Polar API Setup Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md Set up the Polar API on your Convex backend. This involves initializing Polar with your components and defining user info retrieval. ```typescript // convex/billing.ts import { Polar } from "@convex-dev/polar"; import { components } from "./_generated/api"; const polar = new Polar(components.polar, { products: { premiumMonthly: "product_id_1", premiumYearly: "product_id_2", }, getUserInfo: async (ctx) => { const user = await ctx.runQuery(api.auth.getCurrentUser); return { userId: user._id, email: user.email }; }, }); export const { generateCheckoutLink, generateCustomerPortalUrl, ... } = polar.api(); ``` -------------------------------- ### Webhook Handler for Subscription Events Source: https://github.com/get-convex/polar/blob/main/_autodocs/07-usage-examples.md This example shows how to register webhook handlers for subscription events like creation and updates. It demonstrates updating user tiers, sending emails, and revoking access based on subscription status changes. ```typescript // convex/http.ts (partial) polar.registerRoutes(http, { events: { "subscription.created": async (ctx, event) => { const subscription = event.data; // Find customer in database const customer = await ctx.runQuery(api.lib.getCustomerByUserId, { userId: subscription.customerId, // Or however you link customers }); if (customer) { // Update user tier await ctx.runMutation(api.users.updateTier, { userId: customer.userId, tier: "premium", }); // Send welcome email await ctx.runAction(api.email.sendWelcomeEmail, { userId: customer.userId, productName: event.data.productId, }); } }, "subscription.updated": async (ctx, event) => { const subscription = event.data; if (subscription.status === "canceled") { // Revoke premium access const customer = await ctx.runQuery(api.lib.getCustomerByUserId, { userId: subscription.customerId, }); if (customer) { await ctx.runMutation(api.users.updateTier, { userId: customer.userId, tier: "free", }); // Notification await ctx.runAction(api.email.sendSubscriptionCanceledEmail, { userId: customer.userId, }); } } }, }, }); ``` -------------------------------- ### Call syncProducts Action Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Example of how to define and call the `syncProducts` action within your Convex application. This demonstrates the action's handler and how to invoke it from app code. ```typescript export const syncProducts = action({ args: {}, handler: async (ctx) => { await polar.syncProducts(ctx); }, }); // In app code await runAction(api.billing.syncProducts, {}); ``` -------------------------------- ### Run getCurrentSubscription Query Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Example of fetching the current active subscription for a user. This includes the product information and handles cases where no subscription is active or the trial has ended. ```typescript const subscription = await ctx.runQuery(api.lib.getCurrentSubscription, { userId: user._id }); if (subscription) { console.log("Current plan:", subscription.product.name); } ``` -------------------------------- ### Initialize Polar Client in Convex Source: https://github.com/get-convex/polar/blob/main/_autodocs/README.md Initialize the Polar client in your Convex backend, providing product IDs and a user info retrieval function. This setup is typically done in a file like `convex/billing.ts`. ```typescript // convex/billing.ts import { Polar } from "@convex-dev/polar"; const polar = new Polar(components.polar, { products: { premiumMonthly: "prod_123", }, getUserInfo: async (ctx) => ({ userId: user._id, email: user.email, }), }); export const { generateCheckoutLink, ... } = polar.api(); ``` -------------------------------- ### Test Subscription Access Source: https://github.com/get-convex/polar/blob/main/_autodocs/07-usage-examples.md This example shows how to test subscription access by simulating user tiers and verifying document access levels. It uses the ConvexTest utility to run actions and interact with the database. ```typescript // convex/test.example.ts import { internal } from "./_generated/api"; import { ConvexTest } from "convex-test"; async function testSubscriptionAccess() { const t = new ConvexTest(); // Create test user const userId = await t.run(async (ctx) => { return ctx.db.insert("users", { email: "test@example.com", tier: "free", }); }); // Verify free plan restrictions const freeDocuments = await t.run(internal.documents.listDocuments, { userId, }); // Simulate premium subscription await t.run(async (ctx) => { const customer = await ctx.db.insert("customers", { id: "cus_test", userId, }); await ctx.db.insert("subscriptions", { id: "sub_test", customerId: customer, productId: "prod_pro", status: "active", // ... other fields }); }); // Verify premium access const premiumDocuments = await t.run(internal.documents.listDocuments, { userId, }); console.assert( premiumDocuments.length > freeDocuments.length, "Premium should have more documents" ); } ``` -------------------------------- ### CustomerPortalLink in a Settings Page Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md Provides an example of integrating CustomerPortalLink within a larger settings page structure, including styling and setting a dynamic `returnUrl`. ```typescript export function SettingsPage() {\n return (\n
\n

Account Settings

\n \n
\n

Subscription

\n Edit Subscription
); } ``` -------------------------------- ### Polar Client API Reference Source: https://github.com/get-convex/polar/blob/main/_autodocs/MANIFEST.txt Documentation for the main Polar client API, including the constructor and over 15 public methods. This section details instance properties, type exports, and provides complete code examples for each method. ```APIDOC ## Polar Class Constructor ### Description Initializes a new instance of the Polar client with specified parameters. ### Parameters - **options** (object) - Required - Configuration options for the Polar client. - **organizationId** (string) - Required - The unique identifier for the organization. - **apiKey** (string) - Required - The API key for authentication. - **webhookSecret** (string) - Optional - The secret used for verifying webhook signatures. - **convexUrl** (string) - Optional - The URL for the Convex deployment. ### Request Example ```javascript import { Polar } from '@convex-dev/polar'; const polar = new Polar({ organizationId: 'your-org-id', apiKey: 'your-api-key', webhookSecret: 'your-webhook-secret', convexUrl: 'https://your-convex-deployment.convex.cloud' }); ``` ## Public Methods ### Description This section documents over 15 public methods available on the Polar client instance, covering various functionalities for interacting with the Polar service. ### Method Signature Examples (Specific method signatures and details are documented in the `01-polar-client.md` file.) - **getSubscription(subscriptionId: string): Promise** - **createCheckoutSession(options: CheckoutSessionOptions): Promise - **listProducts(): Promise ### Code Examples (Complete code examples for each method are provided in the `01-polar-client.md` file.) ``` -------------------------------- ### Initialize Polar API Functions Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Call `polar.api()` to get an object with Convex-wrapped query and action functions. These functions automatically handle context binding. ```typescript export const { changeCurrentSubscription, cancelCurrentSubscription, getConfiguredProducts, listAllProducts, generateCheckoutLink, generateCustomerPortalUrl, } = polar.api(); ``` -------------------------------- ### Idempotent Webhook Handler for Subscription Creation Source: https://github.com/get-convex/polar/blob/main/_autodocs/05-webhook-integration.md This example demonstrates an idempotent operation for a 'subscription.created' event. It checks for an existing subscription before processing to prevent duplicate actions, ensuring safe retries. ```typescript "subscription.created": async (ctx, event) => { const subId = event.data.id; // Instead of always inserting, check if exists const existing = await ctx.runQuery(api.lib.getSubscription, { id: subId }); if (existing) { console.log("Already processed:", subId); return; } // Safe to process await markUserAsPremium(event.data.customerId); } ``` -------------------------------- ### Get Current Subscription Details Source: https://github.com/get-convex/polar/blob/main/README.md Fetches the current subscription details for a given user. This is suitable for applications with a single active subscription per user. Ensure you have the necessary user context and Polar setup. ```typescript const { productKey, status, currentPeriodEnd, currentPeriodStart, ... } = await polar.getCurrentSubscription(ctx, { userId: user._id, }); ``` -------------------------------- ### Dynamically Load All Products Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md If you prefer not to configure keys statically, use `listAllProducts()` to fetch all available products. You can then filter or sort them as needed. ```typescript const allProducts = await polar.listAllProducts(ctx); // Filter/sort as needed const monthlyPlans = allProducts.filter( p => p.recurringInterval === "month" ); const yearlyPlans = allProducts.filter( p => p.recurringInterval === "year" ); ``` -------------------------------- ### Get Current User Subscription Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Gets the current active subscription for a user. Returns null if no active subscription exists or if the trial has expired. Includes productKey if the product is in the configured products map. ```typescript async getCurrentSubscription( ctx: QueryCtx | MutationCtx | ActionCtx, { userId }: { userId: string } ) ``` ```typescript const subscription = await polar.getCurrentSubscription(ctx, { userId: user._id }); if (subscription) { console.log("Status:", subscription.status); console.log("Product Key:", subscription.productKey); console.log("Period Ends:", subscription.currentPeriodEnd); } ``` -------------------------------- ### Initialize Polar Client in Convex Backend Source: https://github.com/get-convex/polar/blob/main/README.md Initialize the Polar client with user info retrieval and product configuration. ```typescript // convex/example.ts import { Polar } from "@convex-dev/polar"; import { api, components } from "./_generated/api"; import { DataModel } from "./_generated/dataModel"; export const polar = new Polar(components.polar, { // Required: provide a function the component can use to get the current user's ID and // email - this will be used for retrieving the correct subscription data for the // current user. The function should return an object with `userId` and `email` // properties. getUserInfo: async (ctx) => { const user = await ctx.runQuery(api.example.getCurrentUser); return { userId: user._id, email: user.email, }; }, // Optional: Configure static keys for referencing your products. // Alternatively you can use the `listAllProducts` function to get // the product data and sort it out in your UI however you like // (eg., by price, name, recurrence, etc.). // Map your product keys to Polar product IDs (you can also use env vars for this) // Replace these keys with whatever is useful for your app (eg., "pro", "proMonthly", // whatever you want), and replace the values with the actual product IDs from your // Polar dashboard products: { premiumMonthly: "product_id_from_polar", premiumYearly: "product_id_from_polar", premiumPlusMonthly: "product_id_from_polar", premiumPlusYearly: "product_id_from_polar", }, // Optional: Set Polar configuration directly in code organizationToken: "your_organization_token", // Defaults to POLAR_ORGANIZATION_TOKEN env var webhookSecret: "your_webhook_secret", // Defaults to POLAR_WEBHOOK_SECRET env var server: "sandbox", // Optional: "sandbox" or "production", defaults to POLAR_SERVER env var }); // Export API functions from the Polar client export const { changeCurrentSubscription, cancelCurrentSubscription, getConfiguredProducts, listAllProducts, listAllSubscriptions, generateCheckoutLink, generateCustomerPortalUrl, } = polar.api(); ``` -------------------------------- ### Create Polar Client Instance Source: https://github.com/get-convex/polar/blob/main/_autodocs/00-reference-index.md Initialize the Polar client with necessary configurations, including product details and a user info retrieval function. Export the relevant API functions. ```typescript const polar = new Polar(components.polar, { products: { /* ... */ }, getUserInfo: async (ctx) => { /* ... */ }, }); export const { /* ... */ } = polar.api(); ``` -------------------------------- ### Get Product by ID Source: https://github.com/get-convex/polar/blob/main/README.md Retrieve a single product using its Polar product ID. ```typescript const product = await polar.getProduct(ctx, { productId }); ``` -------------------------------- ### Get Customer by User ID Source: https://github.com/get-convex/polar/blob/main/README.md Retrieve the Polar customer record associated with a given user ID. ```typescript const customer = await polar.getCustomerByUserId(ctx, userId); ``` -------------------------------- ### getCurrentSubscription Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Gets the current active subscription for a user. Returns null if no active subscription exists or if the trial has expired. ```APIDOC ## getCurrentSubscription(ctx, { userId }) ### Description Gets the current active subscription for a user. Returns null if no active subscription exists or if the trial has expired. Use this for apps that only allow one active subscription per user. ### Method Not applicable (SDK method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **ctx** (Context) - Required - Convex query, mutation, or action context - **userId** (string) - Required - The Convex user ID ### Returns Promise<{ ...Subscription, productKey?: keyof Products, product: Product } | null> ### Example ```typescript const subscription = await polar.getCurrentSubscription(ctx, { userId: user._id }); if (subscription) { console.log("Status:", subscription.status); console.log("Product Key:", subscription.productKey); console.log("Period Ends:", subscription.currentPeriodEnd); } ``` ``` -------------------------------- ### Initialize Polar Client Source: https://github.com/get-convex/polar/blob/main/_autodocs/00-reference-index.md Instantiate the Polar client with your Convex components, product information, user authentication, and API keys. Ensure environment variables for organization token and webhook secret are set. ```typescript import { Polar } from "@convex-dev/polar"; const polar = new Polar(components.polar, { products: { /* product keys to IDs */ }, getUserInfo: async (ctx) => { /* return userId and email */ }, organizationToken: process.env.POLAR_ORGANIZATION_TOKEN, webhookSecret: process.env.POLAR_WEBHOOK_SECRET, server: "production", }); ``` -------------------------------- ### List All Products Source: https://github.com/get-convex/polar/blob/main/README.md Retrieve a list of all available products and their associated prices. ```typescript const products = await polar.listProducts(ctx); ``` -------------------------------- ### Get Current Subscription Source: https://github.com/get-convex/polar/blob/main/README.md Retrieve the current user's active subscription. Returns null if no active subscription exists. Expired trials are excluded. ```typescript const subscription = await polar.getCurrentSubscription(ctx, { userId }); ``` -------------------------------- ### Polar Constructor Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Initializes a new Polar client instance. This is the primary way to set up the Polar client with your application's configuration. ```APIDOC ## new Polar(component, config) ### Description Initializes a new Polar client instance. ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **component** (ComponentApi) - Required - The Polar Convex component reference from `components.polar` - **config.products** (Products) - Optional - Map of product keys to Polar product IDs for easy reference - **config.getUserInfo** (Function) - Required - Async function that returns current user's ID and email from context - **config.organizationToken** (string) - Optional - Polar API organization token - **config.webhookSecret** (string) - Optional - Polar webhook secret for validating events - **config.server** ("sandbox" | "production") - Optional - Polar API environment ### Request Example ```typescript import { Polar } from "@convex-dev/polar"; import { api, components } from "./_generated/api"; import { DataModel } from "./_generated/dataModel"; const polar = new Polar(components.polar, { products: { premiumMonthly: "product_id_1", premiumYearly: "product_id_2", }, getUserInfo: async (ctx) => { const user = await ctx.runQuery(api.example.getCurrentUser); return { userId: user._id, email: user.email }; }, organizationToken: process.env.POLAR_ORGANIZATION_TOKEN, webhookSecret: process.env.POLAR_WEBHOOK_SECRET, server: "production", }); ``` ``` -------------------------------- ### listProducts Source: https://github.com/get-convex/polar/blob/main/README.md Lists all available products and their associated prices. ```APIDOC ## listProducts List all available products and their prices: ```ts const products = await polar.listProducts(ctx); ``` ``` -------------------------------- ### Polar Client Initialization Source: https://github.com/get-convex/polar/blob/main/README.md Configuration options for initializing the Polar client, including user info retrieval, product mapping, and authentication credentials. ```APIDOC ## Polar Client Configuration The `Polar` class accepts a configuration object with the following properties: - `getUserInfo`: Function to get the current user's ID and email. - `products`: (Optional) Map of arbitrarily named keys to Polar product IDs. - `organizationToken`: (Optional) Your Polar organization token. Falls back to `POLAR_ORGANIZATION_TOKEN` env var. - `webhookSecret`: (Optional) Your Polar webhook secret. Falls back to `POLAR_WEBHOOK_SECRET` env var. - `server`: (Optional) Polar server environment: "sandbox" or "production". Falls back to `POLAR_SERVER` env var. ``` -------------------------------- ### Deploy New Version Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md Releases a new stable version of the project. ```sh npm run release ``` -------------------------------- ### CheckoutLink with Specified Locale Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md Sets the locale for the checkout page using the `locale` prop. This example sets the locale to French (`"fr"`). ```typescript S'abonner ``` -------------------------------- ### Query User and Subscription Details Source: https://github.com/get-convex/polar/blob/main/README.md Use this query to fetch a user's basic information along with their current subscription status. It categorizes the subscription as free or premium based on the product key. ```typescript // convex/example.ts // A query that returns a user with their subscription details export const getCurrentUser = query({ handler: async (ctx) => { const user = await ctx.db.query("users").first(); if (!user) throw new Error("No user found"); const subscription = await polar.getCurrentSubscription(ctx, { userId: user._id, }); return { ...user, subscription, isFree: !subscription, isPremium: subscription?.productKey === "premiumMonthly" || subscription?.productKey === "premiumYearly", }; }, }); ``` -------------------------------- ### Run getProduct Query Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Demonstrates how to execute the `getProduct` query using a product ID. This function returns the product details or null if not found. ```typescript const product = await ctx.runQuery(api.lib.getProduct, { id: "product_123" }); ``` -------------------------------- ### Get Product by Product ID Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Retrieves a single product by its Polar product ID. Requires a Convex context and the product ID. Returns null if the product is not found. ```typescript getProduct( ctx: QueryCtx | MutationCtx | ActionCtx, { productId }: { productId: string } ) ``` ```typescript const product = await polar.getProduct(ctx, { productId: "product_id_from_polar" }); if (product) { console.log("Product:", product.name, product.prices); } ``` -------------------------------- ### Basic CheckoutLink Usage Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md Renders a basic `CheckoutLink` component to upgrade to a premium plan. Ensure `polarApi` is correctly passed from your Convex API setup and `productIds` are valid. ```typescript import { CheckoutLink } from "@convex-dev/polar/react"; import { api } from "../convex/_generated/api"; export function UpgradeButton({ products }) { return ( Upgrade to Premium ); } ``` -------------------------------- ### Get Customer by User ID Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Retrieves the Polar customer record associated with a Convex user ID. Requires a Convex query, mutation, or action context and the user ID. ```typescript getCustomerByUserId(ctx: QueryCtx | MutationCtx | ActionCtx, userId: string) ``` ```typescript const customer = await polar.getCustomerByUserId(ctx, userId); if (customer) { console.log("Customer Polar ID:", customer.id); } ``` -------------------------------- ### Register Webhook Event Handlers Source: https://github.com/get-convex/polar/blob/main/_autodocs/03-types-and-models.md Example of registering webhook event handlers with Polar. This demonstrates how to listen for specific event types like `subscription.created` and `subscription.updated` and access their data. ```typescript polar.registerRoutes(http, { events: { "subscription.created": async (ctx, event) => { // event.type === "subscription.created" // event.data is a Subscription const subscription = event.data; console.log("New subscription:", subscription.id); }, "subscription.updated": async (ctx, event) => { // event.type === "subscription.updated" const subscription = event.data; if (subscription.status === "canceled") { console.log("Subscription canceled"); } }, }, }); ``` -------------------------------- ### Configure Polar Billing and API Source: https://github.com/get-convex/polar/blob/main/_autodocs/05-webhook-integration.md Set up the Polar instance with product definitions and a user info retrieval function. This configuration is essential for Polar to manage billing-related operations. ```typescript // convex/billing.ts import { Polar } from "@convex-dev/polar"; import { api, components } from "./_generated/api"; const polar = new Polar(components.polar, { products: { premium: "product_id_1", }, getUserInfo: async (ctx) => { const user = await ctx.runQuery(api.auth.getCurrentUser); return { userId: user._id, email: user.email }; }, }); export const { /* ... */ } = polar.api(); ``` -------------------------------- ### Display All Products with `listAllProducts` Source: https://github.com/get-convex/polar/blob/main/README.md This React component displays products and prices when products are not configured by key. It fetches all products, finds monthly and yearly plans, and renders their details. You can also sort products client-side or in a Convex query. ```tsx function PricingTable() { const products = useQuery(api.example.listAllProducts); if (!products) return null; // You can sort through products in the client as below, or you can use // `polar.listAllProducts` in your own Convex query and return your desired // products to display in the UI. const proMonthly = products.find( (p) => p.prices[0].recurringInterval === "month", ); const proYearly = products.find( (p) => p.prices[0].recurringInterval === "year", ); return (
{proMonthly && (

{proMonthly.name}

${(proMonthly.prices[0].priceAmount ?? 0) / 100}/ {proMonthly.prices[0].recurringInterval}

)} {proYearly && (

{proYearly.name}

${(proYearly.prices[0].priceAmount ?? 0) / 100}/year

)}
); } ``` -------------------------------- ### Define getCurrentSubscription Query Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Defines a query to get a user's active subscription, including associated product details. It excludes expired trials and returns null if no active subscription exists. ```typescript export const getCurrentSubscription = query({ args: { userId: v.string() }, returns: v.union( v.object({ ...schema.tables.subscriptions.validator.fields, product: schema.tables.products.validator, }), v.null(), ), handler: async (ctx, args) => { ... } }); ``` -------------------------------- ### Configure Products Statically Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Map arbitrary keys to Polar product IDs for static configuration. Use these keys later in your app to retrieve product information. ```typescript const polar = new Polar(components.polar, { products: { // Keys are yours to choose basicMonthly: "prod_abc123", basicYearly: "prod_def456", proMonthly: "prod_ghi789", proYearly: "prod_jkl012", enterpriseCustom: "prod_mno345", }, getUserInfo: /* ... */, }); // Use in your app const products = await polar.getConfiguredProducts(ctx); console.log(products.basicMonthly); console.log(products.proYearly); ``` -------------------------------- ### Billing Logic with Polar Source: https://github.com/get-convex/polar/blob/main/_autodocs/07-usage-examples.md Initialize and use the Polar SDK to manage products, subscriptions, and user information. This includes defining product IDs and handling user data retrieval. ```typescript import { Polar } from "@convex-dev/polar"; import { api, components } from "./_generated/api"; import { DataModel } from "./_generated/dataModel"; import { query } from "./_generated/server"; const polar = new Polar(components.polar, { products: { starterMonthly: "prod_starter_monthly", starterYearly: "prod_starter_yearly", proMonthly: "prod_pro_monthly", proYearly: "prod_pro_yearly", }, getUserInfo: async (ctx) => { const user = await ctx.runQuery(api.auth.getCurrentUser); return { userId: user._id, email: user.email, }; }, organizationToken: process.env.POLAR_ORGANIZATION_TOKEN, webhookSecret: process.env.POLAR_WEBHOOK_SECRET, server: process.env.POLAR_SERVER as "sandbox" | "production", }); // Export typesafe API functions export const { changeCurrentSubscription, cancelCurrentSubscription, getConfiguredProducts, listAllProducts, listAllSubscriptions, generateCheckoutLink, generateCustomerPortalUrl, } = polar.api(); // Additional query for subscription details export const getSubscriptionDetails = query({ args: {}, handler: async (ctx) => { const user = await ctx.runQuery(api.auth.getCurrentUser); const subscription = await polar.getCurrentSubscription(ctx, { userId: user._id, }); return { subscription, isPremium: subscription?.productKey === "proMonthly" || subscription?.productKey === "proYearly", }; }, }); export { polar }; ``` -------------------------------- ### Registering Webhook Routes Source: https://github.com/get-convex/polar/blob/main/_autodocs/05-webhook-integration.md This example demonstrates how to register webhook routes for Polar events using the `registerRoutes` method on your Convex HTTP router. You can specify a custom path and define handlers for specific events. ```APIDOC ## POST /polar/events ### Description Registers webhook handlers for Polar events, allowing real-time synchronization of subscription and product data. ### Method POST ### Endpoint /polar/events (customizable via `path` option) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None (The actual event payload is sent in the request body by Polar) ### Request Example (Polar sends event data in the request body, not shown here as it's from the external service) ### Response #### Success Response (200) - **status** (string) - Indicates successful processing of the webhook event. #### Response Example ```json { "status": "success" } ``` ### registerRoutes Method Signature ```typescript registerRoutes( http: HttpRouter, { path, events, onSubscriptionCreated, onSubscriptionUpdated, onProductCreated, onProductUpdated, }?: { path?: string; events?: WebhookEventHandlers; onSubscriptionCreated?: (ctx, event) => Promise; onSubscriptionUpdated?: (ctx, event) => Promise; onProductCreated?: (ctx, event) => Promise; onProductUpdated?: (ctx, event) => Promise; } ) ``` ### registerRoutes Method Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | http | HttpRouter | Yes | — | Convex HTTP router instance | | options.path | string | No | "/polar/events" | Webhook endpoint path | | options.events | WebhookEventHandlers | No | — | Typesafe event handler map | | options.onSubscriptionCreated | Function | No | — | **Deprecated**: Use events instead | | options.onSubscriptionUpdated | Function | No | — | **Deprecated**: Use events instead | | options.onProductCreated | Function | No | — | **Deprecated**: Use events instead | | options.onProductUpdated | Function | No | — | **Deprecated**: Use events instead | ### Path Customization Example ```typescript // convex/http.ts import { httpRouter } from "convex/server"; import { polar } from "./billing"; const http = httpRouter(); polar.registerRoutes(http, { path: "/webhooks/polar", // Custom path events: { "subscription.created": async (ctx, event) => { // Handle subscription creation console.log("Subscription created:", event); }, "product.created": async (ctx, event) => { // Handle product creation console.log("Product created:", event); } }, }); export default http; ``` **Note**: The Webhook URL would then be `https://your-domain.convex.site/webhooks/polar`. ``` -------------------------------- ### Expose Local Server with ngrok Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Use ngrok to expose your local Convex development server to the internet for webhook testing. Update your webhook URL to the ngrok endpoint. ```bash npx convex dev # Start Convex dev server ngrok http 3210 # Expose to internet # Update webhook URL to ngrok endpoint ``` -------------------------------- ### Create Polar Client Instance Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Instantiate the Polar client in your Convex project, providing user info and optional configurations. ```typescript // convex/billing.ts import { Polar } from "@convex-dev/polar"; import { api, components } from "./_generated/api"; import { DataModel } from "./_generated/dataModel"; const polar = new Polar(components.polar, { // Required: provide user info getUserInfo: async (ctx) => { const user = await ctx.runQuery(api.auth.getCurrentUser); return { userId: user._id, email: user.email, }; }, // Optional: configure products by key products: { premiumMonthly: "prod_xxxxx", premiumYearly: "prod_xxxxx", }, // Optional: configure credentials in code // (defaults to env vars if not provided) organizationToken: process.env.POLAR_ORGANIZATION_TOKEN, webhookSecret: process.env.POLAR_WEBHOOK_SECRET, server: "production", }); export const { changeCurrentSubscription, cancelCurrentSubscription, getConfiguredProducts, listAllProducts, listAllSubscriptions, generateCheckoutLink, generateCustomerPortalUrl, } = polar.api(); // For internal use export { polar }; ``` -------------------------------- ### List Products (Active and Archived) Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Lists all products, optionally including archived ones. Requires a Convex context. Defaults to only active products. ```typescript listProducts( ctx: QueryCtx | MutationCtx | ActionCtx, { includeArchived }: { includeArchived?: boolean } = {} ) ``` ```typescript const activeProducts = await polar.listProducts(ctx); const allProducts = await polar.listProducts(ctx, { includeArchived: true }); ``` -------------------------------- ### CustomerPortalLink with Return URL Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md Shows how to use the `returnUrl` prop with CustomerPortalLink to specify a URL for redirection after leaving the customer portal. ```typescript Manage Billing ``` -------------------------------- ### Run Project Tests Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md Cleans previous builds, builds the project, performs type checking, linting, and runs tests. ```sh npm run clean npm run build npm run typecheck npm run lint npm run test ``` -------------------------------- ### Deploy Alpha Release Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md Releases an alpha version of the project. ```sh npm run alpha ``` -------------------------------- ### List Products Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Lists all available products. Optionally include archived products by setting `includeArchived` to true. Defaults to only active products. ```typescript export const listProducts = query({ args: { includeArchived: v.optional(v.boolean()) }, returns: v.array( v.object({ ...schema.tables.products.validator.fields, priceAmount: v.optional(v.number()), }), ), handler: async (ctx, args) => { ... } }); ``` ```typescript const activeProducts = await ctx.runQuery(api.lib.listProducts, {}); const allProducts = await ctx.runQuery(api.lib.listProducts, { includeArchived: true }); ``` -------------------------------- ### Initialize Polar Client with DataModel Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Instantiate the Polar client, providing a strongly typed DataModel for type safety. Ensure your configuration object adheres to TypeScript's expectations. ```typescript import { Polar } from "@convex-dev/polar"; import { DataModel } from "./_generated/dataModel"; // Strongly typed with your custom DataModel const polar = new Polar(components.polar, { // TypeScript ensures config is correct getUserInfo: async (ctx) => { /* ... */ }, products: { /* ... */ }, }); // Exported functions are typed for your project export const { generateCheckoutLink, // ... other functions ... } = polar.api(); ``` -------------------------------- ### listProducts Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Lists all products, optionally including archived ones. ```APIDOC ## listProducts ### Description Lists all products, optionally including archived ones. ### Method query ### Endpoint api.lib.listProducts ### Parameters #### Arguments - **includeArchived** (boolean) - Optional - Whether to include archived products ### Returns Product[] ### Example ```typescript const activeProducts = await ctx.runQuery(api.lib.listProducts, {}); const allProducts = await ctx.runQuery(api.lib.listProducts, { includeArchived: true }); ``` ``` -------------------------------- ### Products Table Schema Source: https://github.com/get-convex/polar/blob/main/_autodocs/00-reference-index.md Outlines the schema for the 'products' table, detailing product attributes such as ID, name, description, pricing, and associated media. This is used for defining products and their pricing plans. ```typescript { id: string; name: string; description?: string; organizationId: string; isRecurring: boolean; isArchived: boolean; createdAt: string; modifiedAt?: string; recurringInterval?: string; recurringIntervalCount?: number; trialInterval?: string; trialIntervalCount?: number; metadata?: Record; prices: Price[]; benefits?: Benefit[]; medias: Media[]; _id: Id<"products">; _creationTime: number; } ``` -------------------------------- ### Initialize Polar Client Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Initializes a new Polar client instance. Requires a Polar Convex component reference and configuration including user info retrieval, product mapping, and API credentials. ```typescript constructor( public component: ComponentApi, private config: { products?: Products; getUserInfo: (ctx: QueryCtx | MutationCtx | ActionCtx) => Promise<{ userId: string; email: string; }>; organizationToken?: string; webhookSecret?: string; server?: "sandbox" | "production"; }, ) ``` ```typescript import { Polar } from "@convex-dev/polar"; import { api, components } from "./_generated/api"; import { DataModel } from "./_generated/dataModel"; const polar = new Polar(components.polar, { products: { premiumMonthly: "product_id_1", premiumYearly: "product_id_2", }, getUserInfo: async (ctx) => { const user = await ctx.runQuery(api.example.getCurrentUser); return { userId: user._id, email: user.email }; }, organizationToken: process.env.POLAR_ORGANIZATION_TOKEN, webhookSecret: process.env.POLAR_WEBHOOK_SECRET, server: "production", }); ``` -------------------------------- ### Display Products and Prices Source: https://github.com/get-convex/polar/blob/main/README.md Functions to retrieve and display product information and pricing. ```APIDOC ## getConfiguredProducts ### Description Retrieves configured products and their prices based on keys set in the Polar constructor. ### Usage ```tsx const products = useQuery(api.example.getConfiguredProducts); // ... render products ``` ## listAllProducts ### Description Retrieves all available products and their prices. Useful when products are not configured by key. ### Usage ```tsx const products = useQuery(api.example.listAllProducts); // ... find and render desired products ``` ### Product Details Each product object includes: - `id`: The Polar product ID - `name`: The product name - `description`: Product description - `isRecurring`: Whether the product is a subscription - `recurringInterval`: Billing interval (e.g., "month", "year") - `trialInterval`: Trial period interval (if configured) - `trialIntervalCount`: Number of trial intervals (if configured) - `benefits`: Array of product benefits - `prices`: Array of prices with details on `amountType` (fixed, free, custom, seat_based, metered_unit). ``` -------------------------------- ### Define and Use Typed Product Keys Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Define an interface for your product keys and use it to strongly type the Polar client. This ensures product keys are correctly typed in queries and configurations. ```typescript interface Products { premiumMonthly: string; premiumYearly: string; } const polar = new Polar(components.polar, { products: { premiumMonthly: "prod_123", premiumYearly: "prod_456", }, // ... }); // Products properly typed in queries const result = await polar.getCurrentSubscription(ctx, { userId }); if (result?.productKey === "premiumYearly") { // TypeScript knows this is a valid key } ``` -------------------------------- ### Sync Products from Polar Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Syncs existing products from Polar to the local database. Use this to backfill product data. Requires a Convex action context. ```typescript async syncProducts(ctx: ActionCtx) ``` ```typescript export const syncProducts = action({ args: {}, handler: async (ctx) => { await polar.syncProducts(ctx); }, }); ``` -------------------------------- ### listProducts Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Lists all products, with an option to include archived products. ```APIDOC ## listProducts(ctx, options?) ### Description Lists all products (optionally including archived ones). ### Method Not applicable (SDK method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **ctx** (Context) - Required - Convex query, mutation, or action context - **options.includeArchived** (boolean) - Optional - Whether to include archived products (defaults to false) ### Returns Promise ### Example ```typescript const activeProducts = await polar.listProducts(ctx); const allProducts = await polar.listProducts(ctx, { includeArchived: true }); ``` ``` -------------------------------- ### createCustomerPortalSession Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Creates a customer portal session URL for the user to manage their subscription. ```APIDOC ## createCustomerPortalSession(ctx, { userId, returnUrl? }) ### Description Creates a customer portal session URL for the user to manage their subscription. ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **userId** (string) - Required - Convex user ID - **returnUrl** (string) - Optional - URL to return to after the customer exits the portal ### Request Example ```typescript const { url } = await polar.createCustomerPortalSession(ctx, { userId: user._id, returnUrl: "https://example.com/account", }); window.open(url, "_blank"); ``` ### Response #### Success Response (200) - **url** (string) - The URL for the customer portal session ``` -------------------------------- ### Initialize Polar with Constructor Options Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Initialize the Polar component, providing configuration options directly in the constructor. These options override environment variables. ```typescript const polar = new Polar(components.polar, { // These override env vars organizationToken: "token_from_code", webhookSecret: "secret_from_code", server: "production", }); // Falls back to: // process.env.POLAR_ORGANIZATION_TOKEN // process.env.POLAR_WEBHOOK_SECRET // process.env.POLAR_SERVER (or "sandbox" default) ``` -------------------------------- ### Create Customer Portal Session Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md Generates a URL for the customer portal, allowing users to manage their subscriptions. Provide the user ID and an optional return URL for redirection after portal exit. ```typescript async createCustomerPortalSession( ctx: GenericActionCtx, { userId, returnUrl }: { userId: string; returnUrl?: string } ) ``` ```typescript const { url } = await polar.createCustomerPortalSession(ctx, { userId: user._id, returnUrl: "https://example.com/account", }); window.open(url, "_blank"); ``` -------------------------------- ### Generate Checkout Link Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md When calling `generateCheckoutLink()`, provide required parameters like `productIds`, `origin`, and `successUrl`. Optional configurations include trial details, metadata, subscription ID, and locale. ```typescript const { url } = await generateCheckoutLink({ productIds: ["prod_123"], // Required origin: window.location.origin, // Required (for embedding) successUrl: window.location.href, // Required // Optional configurations trialInterval: "day", // "day", "week", "month", "year" trialIntervalCount: 7, // Number of intervals metadata: { source: "pricing" }, // Custom metadata subscriptionId: "sub_456", // For upgrades locale: "fr", // Language: "en", "fr", "de", etc. }); ``` -------------------------------- ### Basic CustomerPortalLink Usage Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md Demonstrates the basic usage of the CustomerPortalLink component. Ensure the `polarApi` prop is correctly passed with the `generateCustomerPortalUrl` function. ```typescript import { CustomerPortalLink } from "@convex-dev/polar/react";\nimport { api } from "../convex/_generated/api";\n\nexport function AccountSettings() {\n return (\n \n Manage Subscription\n \n );\n} ``` -------------------------------- ### Include Customer Metadata Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md Include metadata when creating customers, such as user ID, tier, and signup date. ```typescript const customer = await customersCreate(polar.polar, { email: "user@example.com", metadata: { userId: user._id, tier: "premium", signupDate: new Date().toISOString(), }, }); ``` -------------------------------- ### Define syncProducts Action Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md Defines the `syncProducts` action for fetching and syncing products from Polar. It requires a Polar access token and the server environment. ```typescript export const syncProducts = action({ args: { polarAccessToken: v.string(), server: v.union(v.literal("sandbox"), v.literal("production")), }, handler: async (ctx, args) => { ... } }); ```