PayKit Documentation
repository·main·Indexed 21 days ago
https://github.com/getpaykit/paykitAn embedded billing framework for TypeScript that allows developers to manage subscriptions, usage-based billing, and entitlements directly in their code and database. PayKit abstracts provider complexities (such as Stripe) using a code-first approach with functions like feature(), plan(), and createPayKit(). It includes a CLI for initialization, database migrations, and plan synchronization, as well as a type-safe client for managing subscriptions and customer portals in the browser.
What's inside PayKit
- PayKit is an embedded Stripe billing framework designed for TypeScript applications. It abstracts the complexity of Stripe's lifecycle code, allowing developers to manage subscriptions, usage-based billing, entitlements, and plans directly within their application logic. Instead of manually wiring up Stripe webhooks and API calls for every billing event, you use PayKit to handle the core billing workflows.
What is PayKit?
mainPayKit is a TypeScript-first, server-side payments orchestration framework designed for modern SaaS. It acts as an orchestration layer between your application and payment providers.
Key responsibilities:
- Normalizes billing events from various providers.
- Maintains a local, minimal billing state in your database.
- Provides a provider-agnostic API for managing checkout, webhooks, and charges.
Important distinction: PayKit does not process payments. It does not move money or run the actual checkout/vaulting logic; it coordinates the providers that do those tasks.
Understand the technology stack in the web app
mainThe
webapplication is a T3 Stack project bootstrapped withcreate-t3-app. It utilizes the following core technologies:- Next.js: React framework for the frontend and API routes.
- NextAuth.js: Authentication solution.
- Prisma & Drizzle: ORMs for database management.
- Tailwind CSS: Utility-first CSS framework for styling.
- tRPC: End-to-end typesafe APIs.
How upgrades and downgrades behave in PayKit
mainPayKit handles plan changes differently depending on whether the target plan is more expensive or less expensive than the current plan:
Upgrades (Higher-priced plans)
Upgrades happen immediately. The old plan ends and the new plan starts right away. Prorated amounts are handled based on your provider settings.
Downgrades (Lower-priced plans)
Downgrades are scheduled. The current plan remains active until the end of the current billing period. PayKit stores the target plan as a scheduled change and transitions the customer once the period ends via a provider webhook.
Cancelling to a Free Plan
To cancel a paid subscription, simply call
subscribe()with the ID of your default free plan. This follows the downgrade pattern: the paid plan stays active until the end of the period, then automatically switches to free.Typography and Fonts
mainPayKit uses specific fonts and content rules:
- Font Families: Use Geist (sans) and Geist Mono (mono) via the CSS variables
--font-sansand--font-mono. - Text Sizes: Prefer standard Tailwind text sizes (
text-xs,text-sm,text-base). Use arbitrary values only for sub-scale sizes. - Content Tone: Avoid using emdashes in user-facing text. Use periods, commas, or shorter sentences instead.
- Font Families: Use Geist (sans) and Geist Mono (mono) via the CSS variables
Supported agents for PayKit skills
mainPayKit skills follow the Agent Skills specification. They are compatible with any coding agent that supports this standard, including:
- Claude Code
- Cursor
- Windsurf
- GitHub Copilot
- and other compatible agents.
Maintain provider-agnostic business logic
mainTo ensure your application can switch providers via configuration without requiring code rewrites, avoid using provider-specific identifiers in your core business logic. Instead, rely exclusively on PayKit's unified abstractions:
- PayKit customer IDs
- PayKit payment-method IDs
- PayKit event names
Webhook Idempotency in PayKit
mainPayKit provides built-in idempotency for all incoming webhooks. It records every event it processes; if a provider sends a duplicate event, PayKit will skip it. This guarantees that youronhandlers will not execute multiple times for the same event, preventing duplicate side effects in your application.Use the normalized model for event handling
mainPayKit uses a 'local-sync' model where incoming provider webhooks are transformed into a consistent, normalized format. When writing event handlers, you should always depend on PayKit event names and PayKit entity shapes rather than the raw JSON payload from the provider. This ensures your business logic remains decoupled from specific provider implementation details.Resume a subscription or change a scheduled downgrade
mainIf a customer has a pending change (like a scheduled downgrade or cancellation), you can use
subscribe()to modify or cancel that pending state:- Resume: Subscribing to the customer's currently active plan clears any pending downgrades or cancellations and resumes the subscription normally.
- Change Target: If a downgrade is already scheduled, calling
subscribe()with a different lower-priced plan will replace the existing scheduled target. Only one downgrade can be pending per group at a time.
// Scenario 1: Resuming a subscription // 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". // Scenario 2: Changing a scheduled target // 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".How plan groups work
mainA group is a collection of plans that are mutually exclusive. A customer can only have one active plan per group at any given time.
Usage Patterns:
- Tiered access: A
basegroup containingfree,pro, andultraplans. - Add-ons: An
addonsgroup for one-off upgrades. - Per-seat: A
seatsgroup for per-user pricing.
Important Rules:
- Every plan marked with
default: truemust belong to a group. - Only one plan per group can be designated as
default: true. - When a customer has no active subscription in a group, PayKit treats them as being on the
defaultplan without creating a formal subscription record until they subscribe.
- Tiered access: A
Understand metered feature balances
mainWhen checking metered features, the
balanceobject provides the following properties:limit: The total units allowed for the current period.remaining: The number of units left before the limit is reached.resetAt: The timestamp indicating when the balance will reset.unlimited: A boolean indicating if the plan grants unlimited usage of this feature.