Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
8cf1953
Remove Polar and make PayKit Stripe-only
maxktz Jun 2, 2026
3cc424f
chore: update agent skill
maxktz Jun 2, 2026
7cfdcb9
fix: address Stripe-only review feedback
maxktz Jun 2, 2026
2e0074b
Set web container width to 72rem
maxktz Jun 2, 2026
8ca8a00
Update dark web theme tokens
maxktz Jun 2, 2026
a9902cd
Use GitHub Dark for code highlighting
maxktz Jun 2, 2026
0cb5549
Remove providers docs section
maxktz Jun 2, 2026
5bc5110
Add Aura Shiki theme
maxktz Jun 2, 2026
0dc1583
Add testimonials section components
maxktz Jun 3, 2026
a1a4878
chore: update DESIGN.md
maxktz Jun 3, 2026
347e1dc
chore: update theme
maxktz Jun 3, 2026
8a067fc
chore: update version text in docs
maxktz Jun 3, 2026
53b05d0
Refine button pressed state
maxktz Jun 3, 2026
0474a6d
Update docs copy page actions
maxktz Jun 3, 2026
e2e57d3
Update site navigation and section chrome
maxktz Jun 3, 2026
47b0324
Refine marketing hero
maxktz Jun 3, 2026
f66fb3e
Refine marketing sections
maxktz Jun 3, 2026
9b628b2
Update homepage feedback content
maxktz Jun 3, 2026
d9d33ff
Stabilize docs copy button width
maxktz Jun 3, 2026
267b6c6
Align docs TOC path lines
maxktz Jun 3, 2026
7299462
Update Fumadocs dependencies
maxktz Jun 3, 2026
13333f9
Adjust docs TOC footer progress
maxktz Jun 3, 2026
6c380a9
Polish docs CTA controls
maxktz Jun 3, 2026
27d343f
Replace web icons with Remix icons
maxktz Jun 3, 2026
a025704
Use Remix icons for Fumadocs chrome
maxktz Jun 3, 2026
4ab1442
feat: update agents.md
maxktz Jun 3, 2026
8e85cfe
Fix mono glyph shaping
maxktz Jun 3, 2026
6c25f20
feat(web): add package manager code blocks
maxktz Jun 3, 2026
3e99b2e
feat(web): add icons to links menu
maxktz Jun 3, 2026
5535e8d
chore(web): polish docs sidebar icons
maxktz Jun 3, 2026
0d79e61
fix(web): avoid theme switcher icon flash
maxktz Jun 3, 2026
d20c03b
chore(web): format readme code snippet
maxktz Jun 3, 2026
8c0cb74
fix(web): align titled code block header height
maxktz Jun 3, 2026
5248c76
chore: update docs
maxktz Jun 4, 2026
2ebc3dd
feat: update docs content layout
maxktz Jun 4, 2026
f631859
Fix docs step heading styles
maxktz Jun 4, 2026
17e7968
docs: use explicit step titles
maxktz Jun 4, 2026
b7e51b4
docs: update package manager command defaults
maxktz Jun 4, 2026
31924a4
docs: tune page shell styles
maxktz Jun 4, 2026
a41142f
docs: add custom sidebar layout
maxktz Jun 4, 2026
ac95d05
docs: use fumadocs search provider
maxktz Jun 4, 2026
fe6e171
docs: add toc preview page
maxktz Jun 4, 2026
43541e4
docs: refine mobile docs navigation
maxktz Jun 4, 2026
0703701
docs: preserve sidebar width when collapsed
maxktz Jun 4, 2026
6749f76
docs: fix docs header spacing
maxktz Jun 4, 2026
3607805
docs: align page and toc top padding
maxktz Jun 4, 2026
4b10854
docs: animate docs sidebar column changes
maxktz Jun 4, 2026
b741544
fix docs static rendering and toc stability
maxktz Jun 5, 2026
7e37ed3
feat: add docs hidden trigger
maxktz Jun 5, 2026
c10d5a3
add animated docs sidebar island
maxktz Jun 5, 2026
a348f6a
add docs sidebar hotkey
maxktz Jun 5, 2026
5965ecf
refactor docs mdx rendering
maxktz Jun 5, 2026
55d2639
highlight package manager commands with shiki
maxktz Jun 5, 2026
120dd4e
match docs description text color
maxktz Jun 5, 2026
ee607b4
remove payment providers docs page
maxktz Jun 5, 2026
34734e1
Flatten docs structure
maxktz Jun 5, 2026
5f09386
Clean up docs heading and link formatting
maxktz Jun 5, 2026
451f4fc
chore: update AGENTS.md
maxktz Jun 5, 2026
fe927f7
feat(docs): add sidebar footer controls
maxktz Jun 5, 2026
8438e6c
fix(docs): cut out bun icon face
maxktz Jun 5, 2026
6bd54f6
docs: remove toc preview page
maxktz Jun 5, 2026
c853ad1
docs: hide guides category
maxktz Jun 5, 2026
95209e6
feat(web): add blog placeholder
maxktz Jun 5, 2026
d341f27
feat(web): add sponsor placeholder
maxktz Jun 5, 2026
cb670f2
fix(web): redirect sponsors route
maxktz Jun 5, 2026
49da93b
fix(web): align mobile brand header
maxktz Jun 5, 2026
d4ab9f5
chore: fix github button mr in header
maxktz Jun 5, 2026
4cb7297
feat(web): add footer theme toggle
maxktz Jun 5, 2026
1b5e277
chore: update color palette
maxktz Jun 6, 2026
6648a55
style(docs): hide code highlight border
maxktz Jun 6, 2026
e85576f
fix: fix hero code block display
maxktz Jun 6, 2026
ed0a253
style(web): refine demo code surfaces
maxktz Jun 6, 2026
fc8b962
style(web): soften dark input color
maxktz Jun 6, 2026
b19a015
fix(web): reserve vertical scrollbar space
maxktz Jun 6, 2026
41e3b9e
fix: docs search fix maring
maxktz Jun 6, 2026
fff1ce0
fix(web): align mobile header spacing
maxktz Jun 6, 2026
c28ecd7
style(web): align mobile section padding
maxktz Jun 6, 2026
ddccd8c
feat: update installation doc
maxktz Jun 6, 2026
35e49c5
fix(web): address PR review feedback
maxktz Jun 6, 2026
4612be4
Merge pull request #197 from getpaykit/feat/web-v2
maxktz Jun 6, 2026
f222f18
fix(web): configure app url for metadata
maxktz Jun 6, 2026
d52818d
fix(web): use static OG image for docs
maxktz Jun 6, 2026
1493580
fix(web): address docs review feedback
maxktz Jun 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/paykit-architecture/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: paykit-architecture
description: Use before architectural, API design, provider integration, billing lifecycle, database model, or product-scope decisions in PayKit.
description: not use automatically
---

# PayKit Architecture
Expand Down
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,25 @@ APIs.
- Keep JSDoc strings short and useful
- while writing JSDoc follow it's standards, such as tags

## References

If you need to inspect source code of libraries or packages, prefer inspecting
cloned source repositories when available, rather than library dist files.

These directories already contain source checkouts for some packages:

- `~/ref/fumadocs` - fumadocs framework / package.

## Behavior

- When asked opinion questions, answer only. Do not edit code unless explicitly asked.
- Never commit, push, or run database migrations unless explicitly asked.
- Never create a partial commit from files that also contain unstaged changes.
If a requested commit overlaps dirty files, either commit the full intended
change or stop and ask. Do not rely on hooks stashing/hiding unstaged changes.
- If a commit hook fails while hiding or restoring unstaged changes, stop
immediately and recover the user's unstaged work before retrying or committing
anything.
- When generating migrations, always provide a name.
- Never edit past migrations; create a new migration instead.
- Never run "deploy" scripts to test anything, only if explicitly asked
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<h3 align="center">The billing framework for TypeScript</h3>

<p align="center">
Define products in code. Any provider. Gate features. Track usage.
Define plans in code. Gate features. Track usage. Webhooks handled for you.
</p>

<p align="center">
Expand All @@ -37,7 +37,6 @@
PayKit is an embedded billing framework for TypeScript apps. It sits inside your app, uses your database, and gives you a single API to manage products, subscriptions, entitlements, and usage billing without touching provider dashboards.

```ts
import { stripe } from "@paykitjs/stripe";
import { createPayKit, feature, plan } from "paykitjs";

const messages = feature({ id: "messages", type: "metered" });
Expand All @@ -57,10 +56,10 @@ const pro = plan({
});

export const paykit = createPayKit({
provider: stripe({
stripe: {
secretKey: process.env.STRIPE_SECRET_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
}),
},
database: process.env.DATABASE_URL!,
products: [free, pro],
});
Expand Down
3 changes: 2 additions & 1 deletion apps/demo/drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import "dotenv/config";
export default defineConfig({
dialect: "postgresql",
schema: "../../packages/paykit/src/database/schema.ts",
out: "../../packages/paykit/src/database/migrations",
dbCredentials: {
url: process.env.POLAR_DATABASE_URL!,
url: process.env.PAYKIT_DATABASE_URL!,
},
migrations: {
schema: "public",
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";

/** @type {import("next").NextConfig} */
const config = {
transpilePackages: ["paykitjs", "@paykitjs/polar", "@paykitjs/stripe", "autumn-js"],
transpilePackages: ["paykitjs", "autumn-js"],
serverExternalPackages: ["pg"],
turbopack: {
root: fileURLToPath(new URL("../..", import.meta.url)),
Expand Down
7 changes: 2 additions & 5 deletions apps/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,14 @@
"tunnel": "set -a; [ -f .env ] && . ./.env; set +a; if [ -n \"$CF_TUNNEL_ID\" ]; then cloudflared tunnel --url http://localhost:3000 run \"$CF_TUNNEL_ID\"; fi",
"paykitjs": "bun ../../packages/paykit/src/cli/index.ts",
"typecheck": "tsc --noEmit",
"push": "bun push:auth && bun push:paykit:polar && bun push:paykit:stripe && bun push:autumn",
"push": "bun push:auth && bun push:paykit && bun push:autumn",
"push:auth": "bunx auth migrate --config src/lib/auth.ts --yes",
"push:paykit:polar": "set -a; [ -f .env ] && . ./.env; set +a; if [ -n \"$POLAR_DATABASE_URL\" ] && [ -n \"$POLAR_ACCESS_TOKEN\" ] && [ -n \"$POLAR_WEBHOOK_SECRET\" ]; then bunx paykitjs push --config paykit.polar.config.ts --yes; else printf '%s\\n' 'Skipping PayKit Polar push: provider env incomplete'; fi",
"push:paykit:stripe": "set -a; [ -f .env ] && . ./.env; set +a; if [ -n \"$STRIPE_DATABASE_URL\" ] && [ -n \"$STRIPE_SECRET_KEY\" ] && [ -n \"$STRIPE_WEBHOOK_SECRET\" ]; then bunx paykitjs push --config paykit.stripe.config.ts --yes; else printf '%s\\n' 'Skipping PayKit Stripe push: provider env incomplete'; fi",
"push:paykit": "set -a; [ -f .env ] && . ./.env; set +a; bunx paykitjs push --config paykit.config.ts --yes",
"push:autumn": "set -a; [ -f .env ] && . ./.env; set +a; if [ -n \"$AUTUMN_SECRET_KEY\" ]; then atmn push; else printf '%s\\n' 'Skipping Autumn push: provider env incomplete'; fi",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
"@base-ui/react": "^1.2.0",
"@paykitjs/polar": "workspace:*",
"@paykitjs/stripe": "workspace:*",
"@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.69.0",
"@trpc/client": "^11.0.0",
Expand Down
4 changes: 4 additions & 0 deletions apps/demo/paykit.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { paykit } from "./src/lib/paykit";

export { paykit };
export default paykit;
4 changes: 0 additions & 4 deletions apps/demo/paykit.polar.config.ts

This file was deleted.

4 changes: 0 additions & 4 deletions apps/demo/paykit.stripe.config.ts

This file was deleted.

20 changes: 4 additions & 16 deletions apps/demo/scripts/push-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,16 @@ async function main() {
env,
);

if (hasEnv(values, ["POLAR_DATABASE_URL", "POLAR_ACCESS_TOKEN", "POLAR_WEBHOOK_SECRET"])) {
console.log("Push PayKit Polar config");
if (hasEnv(values, ["PAYKIT_DATABASE_URL", "STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"])) {
console.log("Push PayKit config");
await runCommand(
"bunx",
["paykitjs", "push", "--config", "paykit.polar.config.ts", "--yes"],
["paykitjs", "push", "--config", "paykit.config.ts", "--yes"],
demoDir,
env,
);
} else {
console.log("Skipping PayKit Polar push: provider env incomplete");
}

if (hasEnv(values, ["STRIPE_DATABASE_URL", "STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"])) {
console.log("Push PayKit Stripe config");
await runCommand(
"bunx",
["paykitjs", "push", "--config", "paykit.stripe.config.ts", "--yes"],
demoDir,
env,
);
} else {
console.log("Skipping PayKit Stripe push: provider env incomplete");
console.log("Skipping PayKit push: env incomplete");
}

if (hasEnv(values, ["AUTUMN_SECRET_KEY"])) {
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/scripts/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const sandboxConfig = {
target: "production",
} as const;

export const paykitPackages = ["paykitjs", "@paykitjs/polar", "@paykitjs/stripe"] as const;
export const paykitPackages = ["paykitjs"] as const;

const scriptsDir = fileURLToPath(new URL(".", import.meta.url));

Expand Down
40 changes: 7 additions & 33 deletions apps/demo/src/app/_components/checkout-page-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { authClient } from "@/lib/auth-client";
import { paykitScenarios } from "@/lib/paykit-scenarios";
import type { PayKitScenario } from "@/lib/paykit-scenarios";
import { api } from "@/trpc/react";

function PayKitTabContent({ scenario }: { scenario: PayKitScenario }) {
function PayKitTabContent() {
return (
<TabsContent value={`paykit-${scenario}`} className="flex flex-col gap-8 pt-4">
<SubscribePanel scenario={scenario} />
<TabsContent value="paykit" className="flex flex-col gap-8 pt-4">
<SubscribePanel />
<Separator />
<section className="flex flex-col gap-3">
<h2 className="text-sm font-medium">Features</h2>
<FeaturesPanel scenario={scenario} />
<FeaturesPanel />
</section>
</TabsContent>
);
Expand All @@ -37,14 +35,8 @@ export function CheckoutPageContent() {
const router = useRouter();
const toastShown = useRef(false);

const configuredPaykitScenarios = paykitScenarios.filter(
(scenario) => scenarios.data?.[scenario.id]?.configured,
);
const hasAutumn = scenarios.data?.autumn?.configured === true;
const availableTabs = [
...configuredPaykitScenarios.map((scenario) => scenario.tab),
...(hasAutumn ? ["autumn-stripe"] : []),
];
const availableTabs = ["paykit", ...(hasAutumn ? ["autumn-stripe"] : [])];
const tab = searchParams.get("tab");
const activeTab = tab && availableTabs.includes(tab) ? tab : availableTabs[0];
const setTab = useCallback(
Expand Down Expand Up @@ -109,18 +101,6 @@ export function CheckoutPageContent() {
);
}

if (!activeTab) {
return (
<div className="space-y-3">
<h1 className="text-2xl font-semibold tracking-tight">Billing</h1>
<p className="text-muted-foreground text-sm">
No billing providers are configured. Add a complete provider env group and restart the
demo.
</p>
</div>
);
}

return (
<div className="flex flex-col gap-8">
<div className="flex items-center justify-between">
Expand All @@ -146,16 +126,10 @@ export function CheckoutPageContent() {
<Separator />
<Tabs value={activeTab} onValueChange={setTab}>
<TabsList>
{configuredPaykitScenarios.map((scenario) => (
<TabsTrigger key={scenario.id} value={scenario.tab}>
{scenario.label}
</TabsTrigger>
))}
<TabsTrigger value="paykit">PayKit</TabsTrigger>
{hasAutumn ? <TabsTrigger value="autumn-stripe">Autumn Stripe</TabsTrigger> : null}
</TabsList>
{configuredPaykitScenarios.map((scenario) => (
<PayKitTabContent key={scenario.id} scenario={scenario.id} />
))}
<PayKitTabContent />
{hasAutumn ? (
<TabsContent value="autumn-stripe" className="flex flex-col gap-8 pt-4">
<AutumnSubscribePanel />
Expand Down
15 changes: 4 additions & 11 deletions apps/demo/src/app/_components/features-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,20 @@ import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import { Skeleton } from "@/components/ui/skeleton";
import { featureCatalog } from "@/lib/demo-catalog";
import type { PayKitScenario } from "@/lib/paykit-scenarios";
import { api } from "@/trpc/react";

function MeteredFeatureRow({
scenario,
featureId,
name,
description,
}: {
description: string;
featureId: string;
name: string;
scenario: PayKitScenario;
}) {
const utils = api.useUtils();
const paykitApi = scenario === "polar" ? api.paykitPolar : api.paykitStripe;
const paykitUtils = scenario === "polar" ? utils.paykitPolar : utils.paykitStripe;
const paykitApi = api.paykit;
const paykitUtils = utils.paykit;
const { data, isLoading } = paykitApi.checkFeature.useQuery({
featureId,
});
Expand Down Expand Up @@ -78,17 +75,15 @@ function MeteredFeatureRow({
}

function BooleanFeatureRow({
scenario,
featureId,
name,
description,
}: {
description: string;
featureId: string;
name: string;
scenario: PayKitScenario;
}) {
const paykitApi = scenario === "polar" ? api.paykitPolar : api.paykitStripe;
const paykitApi = api.paykit;
const { data, isLoading } = paykitApi.checkFeature.useQuery({
featureId,
});
Expand All @@ -110,22 +105,20 @@ function BooleanFeatureRow({
);
}

export function FeaturesPanel({ scenario }: { scenario: PayKitScenario }) {
export function FeaturesPanel() {
return (
<div className="flex flex-col gap-4">
{featureCatalog.map((feat) =>
feat.type === "metered" ? (
<MeteredFeatureRow
key={feat.id}
scenario={scenario}
featureId={feat.id}
name={feat.name}
description={feat.description}
/>
) : (
<BooleanFeatureRow
key={feat.id}
scenario={scenario}
featureId={feat.id}
name={feat.name}
description={feat.description}
Expand Down
Loading
Loading