### Initialize a PayKit project Source: https://paykit.sh/docs/concepts/cli Runs an interactive setup wizard to scaffold configuration files and route handlers. ```shell pnpm dlx paykitjs init ``` ```shell bunx paykitjs init ``` ```shell npx paykitjs init ``` -------------------------------- ### Install Dashboard plugin Source: https://paykit.sh/docs/concepts/plugins Add the @paykitjs/dash package using your preferred package manager. ```bash pnpm add @paykitjs/dash ``` ```bash bun add @paykitjs/dash ``` ```bash npm install @paykitjs/dash ``` -------------------------------- ### Initialize PayKit Client in Browser Source: https://paykit.sh/docs/concepts/client Install the client and create an instance typed to your server paykit export. It uses `import type` to carry server types into the browser without bundling any server code. Ensure `identify` is configured on your server instance for automatic customer resolution. ```typescript import { createPayKitClient } from "paykitjs/client"; import type { paykit } from "@/server/paykit"; export const paykitClient = createPayKitClient(); ``` -------------------------------- ### Install PayKit Package Source: https://paykit.sh/docs/get-started/installation Add the PayKit JavaScript package to your project using your preferred package manager. ```bash pnpm add paykitjs ``` ```bash bun add paykitjs ``` ```bash npm install paykitjs ``` -------------------------------- ### Install PayKit Skills via CLI Source: https://paykit.sh/docs/guides/skills Use the skills CLI to add the PayKit skill pack to your project. This command uses npx and does not require global installation. ```terminal npx skills add getpaykit/skills ``` -------------------------------- ### Install Stripe Provider Adapter Source: https://paykit.sh/docs/get-started/installation Add the Stripe provider adapter to your project to enable Stripe as a payment gateway. This is necessary for integrating Stripe's payment processing capabilities with PayKit. ```bash pnpm add @paykitjs/stripe ``` ```bash bun add @paykitjs/stripe ``` ```bash npm install @paykitjs/stripe ``` -------------------------------- ### Getting a Customer Source: https://paykit.sh/docs/concepts/customers Retrieve a customer's details, including their active subscriptions and entitlements, using `getCustomer`. ```APIDOC ## GET /customers/{id} ### Description Retrieves a customer's details, including active subscriptions and entitlements. ### Method GET ### Endpoint /customers/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the customer. ### Response #### Success Response (200) - **customer** (object) - The customer object. - **id** (string) - The customer's unique identifier. - **email** (string) - The customer's email address. - **name** (string) - The customer's name. - **subscriptions** (array) - An array of active subscriptions, each with `planId`, `status`, `cancelAtPeriodEnd`, `currentPeriodStart`, `currentPeriodEnd`. - **entitlements** (object) - A record keyed by feature ID with `balance`, `limit`, `usage`, `unlimited`, `nextResetAt`. #### Response Example ```json { "customer": { "id": "user_123", "email": "jane@example.com", "name": "Jane Doe", "subscriptions": [ { "planId": "pro", "status": "active", "cancelAtPeriodEnd": false, "currentPeriodStart": "2023-01-01T00:00:00Z", "currentPeriodEnd": "2023-02-01T00:00:00Z" } ], "entitlements": { "feature_x": { "balance": 100, "limit": 100, "usage": 50, "unlimited": false, "nextResetAt": "2023-02-01T00:00:00Z" } } } } ``` ``` -------------------------------- ### Validate PayKit setup Source: https://paykit.sh/docs/concepts/cli Performs a read-only check of configuration, database, and API connectivity without modifying state. ```shell pnpm dlx paykitjs check ``` ```shell bunx paykitjs check ``` ```shell npx paykitjs check ``` -------------------------------- ### Listing Customers Source: https://paykit.sh/docs/concepts/customers Get a paginated list of customers using `listCustomers`. You can filter the results by plan ID. ```APIDOC ## GET /customers ### Description Retrieves a paginated list of customers, with optional filtering by plan. ### Method GET ### Endpoint /customers ### Parameters #### Query Parameters - **limit** (integer) - Optional - The maximum number of customers to return per page. Defaults to 50. - **offset** (integer) - Optional - The number of customers to skip before starting to collect the result set. Defaults to 0. - **planIds** (array of strings) - Optional - Filters the list to include only customers subscribed to the specified plans. ### Response #### Success Response (200) - **data** (array) - An array of customer objects. - **total** (integer) - The total number of customers matching the query. - **hasMore** (boolean) - Indicates if there are more customers available beyond the current page. #### Response Example ```json { "data": [ { "id": "user_123", "email": "jane@example.com", "name": "Jane Doe" }, { "id": "user_456", "email": "john@example.com", "name": "John Smith" } ], "total": 100, "hasMore": true } ``` ``` -------------------------------- ### Mount Next.js PayKit Request Handler Source: https://paykit.sh/docs/get-started/installation Set up a catch-all route handler in Next.js to manage PayKit webhooks and client API requests. This example uses the `paykitHandler` from `paykitjs/handlers/next`. ```typescript import { paykitHandler } from "paykitjs/handlers/next"; import { paykit } from "@/lib/paykit"; export const { GET, POST } = paykitHandler(paykit); ``` -------------------------------- ### Get Customer Details and Subscriptions Source: https://paykit.sh/docs/concepts/customers Retrieve a customer's information, including their active subscriptions and entitlements. Subscriptions include plan details and periods, while entitlements track feature balances and limits. ```javascript const customer = await paykit.getCustomer({ id: "user_123" }); // customer.subscriptions: active subscriptions with planId, status, period dates // customer.entitlements: feature balances keyed by feature ID ``` -------------------------------- ### Mount Generic Web API Request Handler Source: https://paykit.sh/docs/get-started/installation Integrate the PayKit handler with any framework supporting the Web API standard. This example shows mounting the handler to a specific path pattern. ```typescript import { paykit } from "./paykit"; // paykit.handler: (request: Request) => Promise // Mount it on any path that catches /paykit/* app.all("/paykit/*", (req) => paykit.handler(req)); ``` -------------------------------- ### Define Metered and Boolean Features Source: https://paykit.sh/docs/get-started/installation Define features for your billing plans using PayKit's code-first approach. This example shows how to define a metered feature ('messages') and a boolean feature ('pro_models'). ```typescript import { feature, plan } from "paykitjs"; const messages = feature({ id: "messages", type: "metered" }); const proModels = feature({ id: "pro_models", type: "boolean" }); ``` -------------------------------- ### Configure PayKit with Connection String Source: https://paykit.sh/docs/concepts/database Alternatively, pass a database connection string directly to `createPayKit`. The `DATABASE_URL` environment variable should be configured. ```typescript // Or pass a connection string directly export const paykit = createPayKit({ database: process.env.DATABASE_URL, // ... }); ``` -------------------------------- ### Initialize PayKit with Plans Source: https://paykit.sh/docs/concepts/plans-and-features Pass the defined plans array to createPayKit to enable type-safe subscription management. ```typescript import { free, pro, ultra } from "./plans"; export const paykit = createPayKit({ // ... plans: [free, pro, ultra], }); // Plan and feature IDs are now type-safe: await paykit.subscribe({ customerId: "user_123", planId: "pro" }); // ✓ await paykit.subscribe({ customerId: "user_123", planId: "typo" }); // ✗ type error ``` -------------------------------- ### Client-side Subscription Initiation Source: https://paykit.sh/docs/flows/subscription-billing Use the client SDK to initiate a subscription. On receiving a `paymentUrl`, redirect the user's browser to complete the checkout process. ```typescript import { paykitClient } from "@/lib/paykit-client"; ``` -------------------------------- ### Push changes to DB with bun Source: https://paykit.sh/docs/get-started/installation Use this command to apply database migrations and sync plan definitions to provider products via bun. ```bash bunx paykitjs push ``` -------------------------------- ### Create PayKit Instance Source: https://paykit.sh/docs/get-started/installation Initialize and export the main PayKit server instance. This file serves as the primary entry point for all server-side billing operations. ```typescript import { createPayKit } from "paykitjs"; export const paykit = createPayKit({ // ... }); ``` -------------------------------- ### Initialize Lemon Squeezy Provider Source: https://paykit.sh/docs/providers/lemonsqueezy Configure the provider using API keys and webhook secrets from environment variables. ```typescript import { lemonSqueezy } from "paykitjs/providers/lemonsqueezy"; const provider = lemonSqueezy({ apiKey: process.env.LEMONSQUEEZY_API_KEY!, webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET!, }); ``` -------------------------------- ### Push changes to DB with npx Source: https://paykit.sh/docs/get-started/installation Use this command to apply database migrations and sync plan definitions to provider products via npx. ```bash npx paykitjs push ``` -------------------------------- ### Initialize PayKit with Stripe Provider Source: https://paykit.sh/docs/concepts/payment-providers Instantiate PayKit by passing the Stripe provider adapter. Ensure you provide your Stripe secret key and webhook secret from environment variables. ```typescript import { stripe } from "@paykitjs/stripe"; export const paykit = createPayKit({ // ... provider: stripe({ secretKey: process.env.STRIPE_SECRET_KEY!, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!, }), }); ``` -------------------------------- ### Configure PayKit with pg.Pool Source: https://paykit.sh/docs/concepts/database Pass a `pg.Pool` instance to `createPayKit` for database configuration. Ensure the `DATABASE_URL` environment variable is set. ```typescript import { Pool } from "pg"; export const paykit = createPayKit({ database: new Pool({ connectionString: process.env.DATABASE_URL, }), // ... }); ``` -------------------------------- ### Registering customer.updated event handlers Source: https://paykit.sh/docs/concepts/webhook-events Use the on option in createPayKit to listen for subscription or entitlement changes. ```typescript export const paykit = createPayKit({ // ... on: { "customer.updated": ({ payload }) => { console.log("Billing changed for", payload.customerId); console.log("Subscriptions:", payload.subscriptions); }, }, }); ``` -------------------------------- ### Subscribe to a plan Source: https://paykit.sh/docs/concepts/subscriptions Unified method for initiating subscriptions, upgrades, and downgrades. Use the server-side variant for backend operations and the client-side variant for frontend checkout flows. ```javascript const result = await paykit.subscribe({ customerId: "user_123", planId: "pro", successUrl: "https://myapp.com/billing/success", cancelUrl: "https://myapp.com/billing", }); if (result.paymentUrl) { // Redirect user to provider checkout } ``` ```javascript const { paymentUrl } = await paykitClient.subscribe({ planId: "pro", successUrl: "/billing/success", cancelUrl: "/billing", }); if (paymentUrl) { window.location.href = paymentUrl; } ``` -------------------------------- ### Configure PayKit with Stripe Source: https://paykit.sh/docs/providers/stripe Initialize PayKit with the Stripe provider using your secret and webhook keys. ```typescript import { stripe } from "@paykitjs/stripe"; import { createPayKit } from "paykitjs"; export const paykit = createPayKit({ // ... provider: stripe({ secretKey: process.env.STRIPE_SECRET_KEY!, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!, }), }); ``` -------------------------------- ### Initialize PayPal Provider Source: https://paykit.sh/docs/providers/paypal Configuration for the PayPal provider using environment variables for credentials. ```typescript import { paypal } from "paykitjs/providers/paypal"; const provider = paypal({ clientId: process.env.PAYPAL_CLIENT_ID!, clientSecret: process.env.PAYPAL_CLIENT_SECRET!, webhookId: process.env.PAYPAL_WEBHOOK_ID!, }); ``` -------------------------------- ### Push changes to DB with pnpm Source: https://paykit.sh/docs/get-started/installation Use this command to apply database migrations and sync plan definitions to provider products via pnpm. ```bash pnpm dlx paykitjs push ``` -------------------------------- ### Configure PayKit with a plugin Source: https://paykit.sh/docs/concepts/plugins Pass plugin instances to the plugins array within the createPayKit configuration. ```typescript import { dash } from "@paykitjs/dash"; export const paykit = createPayKit({ // ... plugins: [dash()], }); ``` -------------------------------- ### Creating Customers Source: https://paykit.sh/docs/concepts/customers Use `upsertCustomer` to create a new customer or update an existing one. If a default plan is configured, new customers are automatically subscribed. ```APIDOC ## POST /customers ### Description Creates or updates a customer. ### Method POST ### Endpoint /customers ### Request Body - **id** (string) - Required - A unique identifier for the customer (e.g., user ID, org ID). - **email** (string) - Required - The customer's email address. - **name** (string) - Required - The customer's name. ### Request Example ```json { "id": "user_123", "email": "jane@example.com", "name": "Jane Doe" } ``` ### Response #### Success Response (200) - **customer** (object) - The created or updated customer object. - **id** (string) - The customer's unique identifier. - **email** (string) - The customer's email address. - **name** (string) - The customer's name. - **subscriptions** (array) - An array of active subscriptions. - **entitlements** (object) - A record of feature entitlements. #### Response Example ```json { "customer": { "id": "user_123", "email": "jane@example.com", "name": "Jane Doe", "subscriptions": [], "entitlements": {} } } ``` ``` -------------------------------- ### Initiate Subscription and Redirect to Checkout Source: https://paykit.sh/docs/flows/subscription-billing Call `subscribe` to initiate a paid plan subscription. If no payment method is saved, it returns a `paymentUrl` for redirecting the user to checkout. ```typescript const result = await paykit.subscribe({ customerId: "user_123", planId: "pro", successUrl: "https://myapp.com/billing/success", cancelUrl: "https://myapp.com/billing", }); if (result.paymentUrl) { return Response.redirect(result.paymentUrl); } ``` -------------------------------- ### Initialize PayKit Client with Custom Base URL Source: https://paykit.sh/docs/concepts/client If your server instance uses a custom `basePath`, provide the `baseURL` option during client initialization to ensure requests are directed correctly. ```typescript export const paykitClient = createPayKitClient({ baseURL: "/custom/api", }); ``` -------------------------------- ### Practical Usage Pattern Source: https://paykit.sh/docs/concepts/entitlements This pattern demonstrates the recommended approach for integrating PayKit entitlements into your application: first check if the customer is allowed to perform an action, then execute the action, and finally report the usage if the action was successful. ```APIDOC ## Practical Usage Pattern ### Description This pattern outlines the recommended workflow for using PayKit's `check()` and `report()` methods to manage feature access and usage. It ensures that usage is only reported for successful actions and that users are prevented from exceeding their limits. ### Workflow 1. **Check Access**: Before performing an action that consumes a metered resource, use `paykit.check()` to verify if the customer has sufficient balance. 2. **Execute Action**: If `check()` returns `allowed: true`, proceed with executing the action (e.g., generating a response, processing a request). 3. **Report Usage**: If the action is successful, call `paykit.report()` to decrement the customer's balance. 4. **Handle Failures**: If `check()` returns `allowed: false`, return an appropriate error response to the user (e.g., 403 Forbidden). If the action itself fails after checking, do not call `report()`. ### Example Implementation (Node.js/Express) ```javascript export async function POST(request: Request) { const userId = "user_123"; // Assume userId is obtained from the request const input = "some input"; // Assume input is obtained from the request // 1. Check Access const { allowed } = await paykit.check({ customerId: userId, featureId: "messages", }); if (!allowed) { // 4. Handle Failures - Usage limit reached return Response.json({ error: "Usage limit reached" }, { status: 403 }); } try { // 2. Execute Action const response = await generateChatResponse(input); // 3. Report Usage (only if action succeeded) await paykit.report({ customerId: userId, featureId: "messages", amount: 1, }); return Response.json(response); } catch (error) { // Handle potential errors during action execution console.error("Action failed:", error); return Response.json({ error: "An internal error occurred" }, { status: 500 }); } } ``` ### Benefits - Prevents charging usage for failed requests. - Ensures users are not served responses after hitting their limits. - Provides a robust mechanism for managing feature access and metered usage. ``` -------------------------------- ### Define environment variables Source: https://paykit.sh/docs/providers/stripe Add the required Stripe credentials to your .env file. ```text STRIPE_SECRET_KEY=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_... ``` -------------------------------- ### Sync plans and migrations Source: https://paykit.sh/docs/concepts/cli Applies database migrations and synchronizes plan definitions with the payment provider. ```shell pnpm dlx paykitjs push ``` ```shell bunx paykitjs push ``` ```shell npx paykitjs push ``` -------------------------------- ### Define Subscription Plans with PayKit Source: https://paykit.sh/docs/flows/subscription-billing Define metered and boolean features, and create free and pro plans with specific limits and pricing. Ensure plans are grouped correctly for PayKit. ```typescript import { feature, plan } from "paykitjs"; const messages = feature({ id: "messages", type: "metered" }); const proModels = feature({ id: "pro_models", type: "boolean" }); export const free = plan({ id: "free", name: "Free", group: "base", default: true, includes: [messages({ limit: 100, reset: "month" })], }); export const pro = plan({ id: "pro", name: "Pro", group: "base", price: { amount: 19, interval: "month" }, includes: [ messages({ limit: 2_000, reset: "month" }), proModels(), ], }); ``` -------------------------------- ### Create or Update Customer Source: https://paykit.sh/docs/concepts/customers Use `upsertCustomer` to create a new customer or update an existing one. If a default plan is configured, new customers are automatically subscribed. ```javascript await paykit.upsertCustomer({ id: "user_123", email: "jane@example.com", name: "Jane Doe", }); ``` -------------------------------- ### Enable testing mode Source: https://paykit.sh/docs/providers/stripe Configure PayKit to use Stripe test clocks for simulating billing events. ```typescript export const paykit = createPayKit({ // ... testing: { enabled: true, }, }); ``` -------------------------------- ### Configure PayKit with Database Pool Source: https://paykit.sh/docs/get-started/installation Set up PayKit to use a PostgreSQL database for storing billing state. You can provide a connection string or an existing `pg.Pool` instance. PayKit will create tables prefixed with `paykit_`. ```typescript import { Pool } from "pg"; export const paykit = createPayKit({ // ... database: new Pool({ connectionString: process.env.DATABASE_URL!, }), }); ``` -------------------------------- ### Initialize Paddle Provider Source: https://paykit.sh/docs/providers/paddle Use this code to initialize the Paddle provider with your API key and webhook secret. Ensure environment variables are set. ```javascript import { paddle } from "paykitjs/providers/paddle"; const provider = paddle({ apiKey: process.env.PADDLE_API_KEY!, webhookSecret: process.env.PADDLE_WEBHOOK_SECRET!, }); ``` -------------------------------- ### Configure Default Plans Source: https://paykit.sh/docs/concepts/plans-and-features Set a plan as the default for a group to act as a fallback when no subscription exists. ```typescript export const free = plan({ id: "free", group: "base", default: true, // ... }); ``` -------------------------------- ### Registering Event Handlers Source: https://paykit.sh/docs/concepts/webhook-events How to register handlers for specific PayKit events or all events using the 'on' configuration option. ```APIDOC ## Registering Event Handlers ### Description Register handlers for PayKit events to trigger application logic when billing state changes occur. ### Request Example ```typescript export const paykit = createPayKit({ on: { "customer.updated": ({ payload }) => { console.log("Billing changed for", payload.customerId); }, "*": ({ event }) => { console.log(event.name, event.payload); } } }); ``` ``` -------------------------------- ### Forward webhooks locally Source: https://paykit.sh/docs/providers/stripe Use the Stripe CLI to forward events to your local development server. ```bash stripe listen --forward-to localhost:3000/paykit/api/webhook ``` -------------------------------- ### Initialize Polar Provider Source: https://paykit.sh/docs/providers/polar Configure the Polar provider using an access token from environment variables. ```typescript import { polar } from "paykitjs/providers/polar"; const provider = polar({ accessToken: process.env.POLAR_ACCESS_TOKEN!, }); ``` -------------------------------- ### Sync a customer Source: https://paykit.sh/docs/get-started/quickstart Demonstrates manual server-side customer upserting and automatic client-side identification via session headers. ```typescript // call upsertCustomer when the user is created, or before the purchase: await paykit.upsertCustomer({ id: "user_123", // user or organization id email: "jane@example.com", name: "Jane Doe", }); // then, pass `customerId` to purchase: await paykit.subscribe({ customerId: "user_123", planId: "pro" }) ``` ```typescript // server.ts const paykit = createPayKit({ // ... // client's customer is automatically identified here: identify: async ({ headers }) => { const session = await auth.api.getSession({ headers }); if (!session) return null; return { customerId: session.user.id, // user or organization id email: session.user.email, name: session.user.name, }; }, }); // client.ts const paykitClient = createPayKitClient(); // so you don't need to pass `customerId` to purchase on client: await paykitClient.subscribe({ planId: "pro" }) ``` -------------------------------- ### Upgrade to a Higher-Priced Plan Source: https://paykit.sh/docs/flows/subscription-billing Initiate an upgrade by calling `subscribe` with the ID of the higher-priced plan. The change takes effect immediately, and prorated amounts are handled by the provider. ```typescript await paykit.subscribe({ customerId: "user_123", planId: "pro", // moving up from free }); // subscription is active immediately ``` -------------------------------- ### Initialize Creem Provider Source: https://paykit.sh/docs/providers/creem Import and initialize the Creem provider using your API key and webhook secret. Ensure environment variables are set. ```javascript import { creem } from "paykitjs/providers/creem"; const provider = creem({ apiKey: process.env.CREEM_API_KEY!, webhookSecret: process.env.CREEM_WEBHOOK_SECRET!, }); ``` -------------------------------- ### Subscribe to a plan Source: https://paykit.sh/docs/get-started/quickstart Handles plan subscriptions on both server and client, redirecting to a payment URL when necessary. ```typescript const result = await paykit.subscribe({ customerId: "user_123", planId: "pro", successUrl: "https://myapp.com/billing/success", cancelUrl: "https://myapp.com/billing", }); if (result.paymentUrl) { // redirect user to provider checkout } ``` ```tsx import { paykitClient } from "@/lib/paykit-client"; ``` -------------------------------- ### Open Customer Billing Portal Source: https://paykit.sh/docs/concepts/client Use the `customerPortal` method to open the provider's customer portal. It requires a `returnUrl` and returns a URL that should be used to redirect the user. ```typescript // Open customer billing portal const { url } = await paykitClient.customerPortal({ returnUrl: window.location.href, }); window.location.href = url; ``` -------------------------------- ### Pass Plans to PayKit Configuration Source: https://paykit.sh/docs/get-started/installation Include your defined plans (e.g., 'free' and 'pro') in the PayKit configuration. This makes the plans available for subscription management and billing. ```typescript import { free, pro } from "./plans"; export const paykit = createPayKit({ // ... plans: [free, pro], }); ``` -------------------------------- ### Define Subscription Plans Source: https://paykit.sh/docs/concepts/plans-and-features Use the plan function to define tiers, including their group, pricing, and included features. ```typescript export const free = plan({ id: "free", name: "Free", group: "base", default: true, includes: [ messages({ limit: 100, reset: "month" }), ], }); export const pro = plan({ id: "pro", name: "Pro", group: "base", price: { amount: 19, interval: "month" }, includes: [ messages({ limit: 2_000, reset: "month" }), proModels(), ], }); export const ultra = plan({ id: "ultra", name: "Ultra", group: "base", price: { amount: 49, interval: "month" }, includes: [ messages({ limit: 10_000, reset: "month" }), proModels(), prioritySupport(), ], }); ``` -------------------------------- ### List Customers with Filtering Source: https://paykit.sh/docs/concepts/customers Fetch a paginated list of customers. You can filter the results by plan to identify users on specific subscription tiers. ```javascript const { data, total, hasMore } = await paykit.listCustomers({ limit: 50, offset: 0, planIds: ["pro", "ultra"], }); ``` -------------------------------- ### Customer Identification (Client) Source: https://paykit.sh/docs/concepts/customers Configure PayKit to identify the authenticated customer from incoming requests using the `identify` option during `createPayKit` initialization. ```APIDOC ## Client Initialization with Identification ### Description Configures the PayKit client to identify the authenticated customer from incoming requests. This is crucial for server-side operations and when using PayKit's HTTP router. ### Method Initialization (via `createPayKit`) ### Endpoint N/A (Client-side configuration) ### Parameters #### Request Body (within `createPayKit` options) - **identify** (function) - Required if using client SDK or HTTP router - An asynchronous function that takes the `request` object and returns a customer identifier object or `null`. - The `request` object typically contains headers. - The returned object should have at least `customerId`, `email`, and `name`. - If `null` is returned, the request is treated as unauthenticated. ### Request Example ```javascript import { createPayKit } from '@paykit/node'; import { auth } from './auth'; // Assuming an auth module export const paykit = createPayKit({ // ... other options identify: async (request) => { const session = await auth.api.getSession({ headers: request.headers }); if (!session) return null; return { customerId: session.user.id, email: session.user.email, name: session.user.name, }; }, }); ``` ### Notes - The `identify` function acts as the trust boundary. Any `customerId` explicitly passed in subsequent requests must match the one returned by `identify()`. - If `identify()` returns `null`, PayKit will reject the request as unauthenticated. ``` -------------------------------- ### Configure PayKit Identify for Automatic Customer Creation Source: https://paykit.sh/docs/flows/subscription-billing Set up the `identify` function in PayKit to automatically create or identify customers based on session data. This ensures customers are created on first use. ```typescript export const paykit = createPayKit({ // ... identify: async ({ headers }) => { const session = await auth.api.getSession({ headers }); if (!session) return null; return { customerId: session.user.id, email: session.user.email, name: session.user.name, }; }, }); ``` -------------------------------- ### Define 'Pro' Plan Source: https://paykit.sh/docs/get-started/installation Define the 'Pro' plan with a monthly price, including more 'messages' and access to 'pro_models'. This plan is suitable for users requiring advanced features. ```typescript export const pro = plan({ id: "pro", name: "Pro", group: "base", price: { amount: 19, interval: "month" }, includes: [ messages({ limit: 2000, reset: "month" }), proModels() ], }); ``` -------------------------------- ### Listen to events Source: https://paykit.sh/docs/get-started/quickstart Configures event listeners within the PayKit instance to react to billing changes. ```typescript export const paykit = createPayKit({ // ... on: { "customer.updated": ({ payload }) => { console.log("billing changed for", payload.customerId); }, }, }); ``` -------------------------------- ### Define Plan Pricing Source: https://paykit.sh/docs/concepts/plans-and-features Specify the amount and billing interval for paid plans. ```typescript price: { amount: 19, interval: "month" } ``` -------------------------------- ### Define 'Free' Plan Source: https://paykit.sh/docs/get-started/installation Define the 'Free' plan, which includes a limited number of 'messages' per month. This plan is set as the default plan. ```typescript export const free = plan({ id: "free", name: "Free", group: "base", default: true, includes: [ messages({ limit: 100, reset: "month" }) ], }); ``` -------------------------------- ### Resume a subscription Source: https://paykit.sh/docs/concepts/subscriptions Clears pending downgrades or cancellations by subscribing the customer to their current active plan. ```javascript // Customer is on "pro" with a pending downgrade to "free" await paykit.subscribe({ customerId: "user_123", planId: "pro" }); // Scheduled downgrade is cleared. Customer stays on "pro". ``` -------------------------------- ### Client-Side Type Safety with PayKit SDK Source: https://paykit.sh/docs/concepts/typescript Create a type-safe PayKit client by inheriting types from the server instance. Plan IDs are validated at compile time on the client. ```typescript // Client inherits server types import type { paykit } from "@/server/paykit"; const client = createPayKitClient(); await client.subscribe({ planId: "pro" }); // ✓ type-safe ``` -------------------------------- ### Passing Plans as a Module Object Source: https://paykit.sh/docs/concepts/typescript Organize plans in a separate module and import them as a namespace. PayKit internally normalizes this to an array for consistent type inference. ```typescript // Plans can also be passed as a module object import * as plans from "./plans"; export const paykit = createPayKit({ plans, // ... }); ``` -------------------------------- ### Check Feature Entitlements Source: https://paykit.sh/docs/flows/subscription-billing Use `check()` to gate features based on the customer's active plan. It returns `allowed` status and, for metered features, usage `balance` details. ```typescript const { allowed } = await paykit.check({ customerId: userId, featureId: "messages", }); if (!allowed) { return Response.json({ error: "Usage limit reached" }, { status: 403 }); } // also check boolean features: const { allowed: canUseProModels } = await paykit.check({ customerId: userId, featureId: "pro_models", }); ``` -------------------------------- ### Subscribe to a Plan from React Component Source: https://paykit.sh/docs/concepts/client Call the `subscribe` method from a React component to initiate a subscription. This method, like `customerPortal`, does not require a `customerId` as it's resolved from the incoming request via `identify`. It returns a `paymentUrl` which should be used to redirect the user. ```typescript // Subscribe from a React component ``` -------------------------------- ### Inferred Plan and Feature IDs with PayKit Source: https://paykit.sh/docs/concepts/typescript Define plans and use PayKit methods with type-safe plan and feature IDs. Invalid IDs will result in compile-time errors. ```typescript // Types are inferred from your plans export const paykit = createPayKit({ plans: [free, pro, ultra], // ... }); // planId only accepts "free" | "pro" | "ultra" await paykit.subscribe({ customerId: "user_123", planId: "pro" }); // featureId only accepts "messages" | "pro_models" | "priority_support" await paykit.check({ customerId: "user_123", featureId: "messages" }); ``` ```typescript // Type error: "typo" is not assignable to "free" | "pro" | "ultra" await paykit.subscribe({ customerId: "user_123", planId: "typo" }); ``` -------------------------------- ### Configure Dashboard plugin with authorization Source: https://paykit.sh/docs/concepts/plugins Use the authorize function to restrict dashboard access based on session validation. ```typescript import { dash } from "@paykitjs/dash"; export const paykit = createPayKit({ // ... plugins: [ dash({ authorize: async (request) => { const session = await auth.api.getSession({ headers: request.headers, }); if (!session) throw new Error("Not authenticated"); }, }), ], }); ``` -------------------------------- ### Listen to Subscription Changes Source: https://paykit.sh/docs/flows/subscription-billing Registers an event handler to react to customer updates, such as subscription or entitlement changes. ```typescript export const paykit = createPayKit({ // ... on: { "customer.updated": ({ payload }) => { console.log("billing changed for", payload.customerId); console.log("subscriptions:", payload.subscriptions); // sync to your own data layer, invalidate caches, etc. }, }, }); ``` -------------------------------- ### Subscription Management API Source: https://paykit.sh/docs/concepts/subscriptions The `subscribe()` method is the primary interface for managing customer subscriptions. It handles new subscriptions, upgrades, downgrades, cancellations, and resumptions through a single call. ```APIDOC ## POST /api/subscribe ### Description Manages the subscription lifecycle for a customer, including creating new subscriptions, upgrading, downgrading, canceling, or resuming existing ones. ### Method POST ### Endpoint /api/subscribe ### Parameters #### Request Body - **planId** (string) - Required - The ID of the target plan. Must be a valid plan from your configuration. - **customerId** (string) - Server only - The customer to subscribe. Not needed on the client (resolved from `identify`). - **successUrl** (string) - Recommended - The URL to redirect to after successful checkout. - **cancelUrl** (string) - Recommended - The URL to redirect to if the customer cancels checkout. - **forceCheckout** (boolean) - Optional - Forces a checkout flow even if the customer already has a payment method. ### Request Example ```json { "customerId": "user_123", "planId": "pro", "successUrl": "https://myapp.com/billing/success", "cancelUrl": "https://myapp.com/billing" } ``` ### Response #### Success Response (200) - **paymentUrl** (string) - Present when the customer needs to go through a checkout flow. Redirect them there. - **invoice** (object) - Present when a charge was created immediately. - **requiredAction** (object) - Present when additional authentication (like 3D Secure) is required. #### Response Example ```json { "paymentUrl": "https://provider.com/checkout?token=...", "invoice": { "id": "inv_abc", "amount": 1000, "currency": "USD" } } ``` ### Behavior Summary Scenario| Behavior ---|--- New subscription (free)| Activates immediately New subscription (paid, has payment method)| Creates subscription directly New subscription (paid, no payment method)| Returns `paymentUrl` for checkout Upgrade (higher price, same group)| Switches immediately Downgrade (lower price, same group)| Scheduled for period end Cancel to default free plan| Scheduled for period end Re-subscribe to current plan (pending cancel)| Resumes, clears cancellation Re-subscribe to current plan (no changes)| No-op Change scheduled target| Replaces the pending downgrade target if a new lower-priced plan is selected. ``` -------------------------------- ### Manage telemetry settings Source: https://paykit.sh/docs/concepts/cli Checks the current status of anonymous usage telemetry collection. ```shell pnpm dlx paykitjs telemetry status ``` ```shell bunx paykitjs telemetry status ``` ```shell npx paykitjs telemetry status ``` -------------------------------- ### Schedule a Subscription Downgrade Source: https://paykit.sh/docs/flows/subscription-billing Moves a customer to a lower-priced plan. The current plan remains active until the end of the billing period. ```javascript await paykit.subscribe({ customerId: "user_123", planId: "free", // moving down from pro }); // customer stays on pro until period ends ``` -------------------------------- ### Subscribe using Internal IDs Source: https://paykit.sh/docs/concepts/payment-providers When subscribing a customer, use your application's internal customer and plan IDs. PayKit will internally resolve these to the corresponding provider-native IDs. ```typescript // Your app always uses its own IDs await paykit.subscribe({ customerId: "user_123", planId: "pro" }); // PayKit resolves the Stripe customer and price internally ``` -------------------------------- ### Configure Identify Option for Client Authentication Source: https://paykit.sh/docs/get-started/installation Implement the `identify` option in your PayKit server instance to authenticate incoming client requests. This allows PayKit to associate requests with specific customers based on session data. ```typescript export const paykit = createPayKit({ // ... identify: async ({ headers }) => { const session = await auth.api.getSession({ headers }); if (!session) return null; return { customerId: session.user.id, email: session.user.email, name: session.user.name, // Just pass user's data, and Stripe customer gets synced automatically! }; }, }); ``` -------------------------------- ### Usage billing Source: https://paykit.sh/docs/get-started/quickstart Verifies usage limits with check and records consumption with report in a server-side route handler. ```typescript export async function POST(request: Request) { const { allowed } = await paykit.check({ customerId: userId, featureId: "messages", }); if (!allowed) { return Response.json({ error: "Usage limit reached" }, { status: 403 }); } const response = await generateChatResponse(input); await paykit.report({ customerId: userId, featureId: "messages", amount: 1, }); return Response.json(response); } ``` -------------------------------- ### Check Usage Before Consuming Source: https://paykit.sh/docs/flows/metered-usage Call `paykit.check` before performing an action to determine if the customer has remaining usage balance. It returns an `allowed` boolean and the current `balance`. ```typescript const { allowed, balance } = await paykit.check({ customerId: userId, featureId: "messages", }); ``` -------------------------------- ### Generate customer portal URL Source: https://paykit.sh/docs/providers/stripe Retrieve a URL to redirect users to the Stripe-hosted customer portal. ```javascript const { url } = await paykit.customerPortal({ customerId: "user_123", returnUrl: "https://myapp.com/billing", }); // redirect the user to `url` ``` ```javascript const { url } = await paykitClient.customerPortal({ returnUrl: window.location.href, }); window.location.href = url; ``` -------------------------------- ### Registering a wildcard event handler Source: https://paykit.sh/docs/concepts/webhook-events The * wildcard catches all PayKit events, which is useful for logging or debugging purposes. ```typescript export const paykit = createPayKit({ // ... on: { "*": ({ event }) => { console.log(event.name, event.payload); }, }, }); ``` -------------------------------- ### customer.updated Event Source: https://paykit.sh/docs/concepts/webhook-events Details regarding the customer.updated event payload structure. ```APIDOC ## Event: customer.updated ### Description Fires after any subscription or entitlement change. Use it to sync billing state, invalidate caches, or trigger downstream logic. ### Payload - **customerId** (string) - Your internal customer identifier - **subscriptions** (Subscription[]) - The customer's updated subscriptions ``` -------------------------------- ### Check feature access Source: https://paykit.sh/docs/concepts/entitlements Use the check method to determine if a customer has access to a feature and retrieve their current balance. ```javascript const { allowed, balance } = await paykit.check({ customerId: "user_123", featureId: "messages", }); // allowed: true // balance: { limit: 2000, remaining: 1847, resetAt: "2026-05-01T00:00:00Z", unlimited: false } ``` -------------------------------- ### Report Usage After Action Source: https://paykit.sh/docs/flows/metered-usage After a successful action, call `paykit.report` to decrement the customer's balance. Specify the `amount` consumed, typically 1 for single operations. ```typescript await paykit.report({ customerId: userId, featureId: "messages", amount: 1, }); ``` -------------------------------- ### Update scheduled target Source: https://paykit.sh/docs/concepts/subscriptions Replaces an existing pending downgrade with a new target plan. ```javascript // Customer is on "ultra" with a pending downgrade to "free" await paykit.subscribe({ customerId: "user_123", planId: "pro" }); // Scheduled target changes from "free" to "pro". ``` -------------------------------- ### Customer Identification for HTTP Requests Source: https://paykit.sh/docs/concepts/customers Configure PayKit to identify the authenticated customer from incoming HTTP requests. This is crucial for the client SDK and HTTP router to ensure requests are properly attributed and secured. ```typescript export const paykit = createPayKit({ // ... identify: async (request) => { const session = await auth.api.getSession({ headers: request.headers }); if (!session) return null; return { customerId: session.user.id, email: session.user.email, name: session.user.name, }; }, }); ``` -------------------------------- ### Define a custom plugin Source: https://paykit.sh/docs/concepts/plugins Plugins are objects containing a unique ID and an optional map of endpoint handlers. ```typescript type Plugin = { id: string; endpoints?: Record; }; ``` -------------------------------- ### Define Features Source: https://paykit.sh/docs/concepts/plans-and-features Create boolean access gates or metered usage limits using the feature function. ```typescript import { feature, plan } from "paykitjs"; // Boolean feature: grants access to something const proModels = feature({ id: "pro_models", type: "boolean" }); // Metered feature: tracks usage with a limit const messages = feature({ id: "messages", type: "metered" }); ``` -------------------------------- ### Include Features in Plans Source: https://paykit.sh/docs/concepts/plans-and-features Invoke feature functions within a plan's includes array. Metered features require a limit and reset interval. ```typescript proModels() ``` ```typescript messages({ limit: 100, reset: "month" }) ``` -------------------------------- ### Report Metered Usage Source: https://paykit.sh/docs/concepts/entitlements The `report()` method is called after a customer consumes a metered resource. It decrements the balance for the specified feature and returns the updated state. If the customer lacks sufficient balance, the operation fails and the balance is not decremented. ```APIDOC ## Report Metered Usage ### Description Reports that a customer has consumed a certain amount of a metered resource. This decrements the customer's balance for the specified feature. The operation fails if the customer does not have sufficient balance. ### Method `paykit.report(options)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **customerId** (string) - Required - The unique identifier for the customer. - **featureId** (string) - Required - The unique identifier for the metered feature. - **amount** (number) - Required - The amount of resource consumed. ### Request Example ```json { "customerId": "user_123", "featureId": "messages", "amount": 1 } ``` ### Response #### Success Response (200) - **success** (boolean) - Indicates if the reporting was successful (i.e., sufficient balance was available). - **balance** (object) - The updated balance object after the consumption. - **limit** (number) - The total units allowed for the current period. - **remaining** (number) - The number of units remaining for the current period. - **resetAt** (string) - The timestamp when the balance will reset. - **unlimited** (boolean) - True if the plan grants unlimited usage for this feature. #### Response Example ```json { "success": true, "balance": { "limit": 2000, "remaining": 1846, "resetAt": "2026-05-01T00:00:00Z", "unlimited": false } } ``` ``` -------------------------------- ### Perform Action Conditionally Source: https://paykit.sh/docs/flows/metered-usage Only execute the intended action if the `allowed` flag returned from `paykit.check` is true. If false, return an error indicating the usage limit has been reached. ```typescript if (!allowed) { return Response.json({ error: "Usage limit reached" }, { status: 403 }); } const response = await generateChatResponse(input); ```