paddle-checkout-accelerator 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +388 -0
  3. package/dist/package/index.d.ts +28 -0
  4. package/dist/package/index.js +28 -0
  5. package/dist/src/components/paddle/BillingHistory.d.ts +1 -0
  6. package/dist/src/components/paddle/BillingHistory.js +17 -0
  7. package/dist/src/components/paddle/CustomerPortal.d.ts +1 -0
  8. package/dist/src/components/paddle/CustomerPortal.js +5 -0
  9. package/dist/src/components/paddle/CustomerPortalButton.d.ts +1 -0
  10. package/dist/src/components/paddle/CustomerPortalButton.js +31 -0
  11. package/dist/src/components/paddle/InlineCheckout.d.ts +5 -0
  12. package/dist/src/components/paddle/InlineCheckout.js +13 -0
  13. package/dist/src/components/paddle/PricingTable.d.ts +1 -0
  14. package/dist/src/components/paddle/PricingTable.js +27 -0
  15. package/dist/src/components/paddle/SubscriptionCard.d.ts +6 -0
  16. package/dist/src/components/paddle/SubscriptionCard.js +5 -0
  17. package/dist/src/components/paddle/TrialBanner.d.ts +5 -0
  18. package/dist/src/components/paddle/TrialBanner.js +5 -0
  19. package/dist/src/components/paddle/UpgradeModal.d.ts +11 -0
  20. package/dist/src/components/paddle/UpgradeModal.js +35 -0
  21. package/dist/src/components/paddle/UsageMeter.d.ts +6 -0
  22. package/dist/src/components/paddle/UsageMeter.js +8 -0
  23. package/dist/src/components/paddle/gates/SubscriptionGate.d.ts +7 -0
  24. package/dist/src/components/paddle/gates/SubscriptionGate.js +8 -0
  25. package/dist/src/lib/billing/adapters/index.d.ts +3 -0
  26. package/dist/src/lib/billing/adapters/index.js +3 -0
  27. package/dist/src/lib/billing/adapters/memory.d.ts +2 -0
  28. package/dist/src/lib/billing/adapters/memory.js +41 -0
  29. package/dist/src/lib/billing/adapters/prisma/index.d.ts +28 -0
  30. package/dist/src/lib/billing/adapters/prisma/index.js +80 -0
  31. package/dist/src/lib/billing/adapters/types.d.ts +13 -0
  32. package/dist/src/lib/billing/adapters/types.js +1 -0
  33. package/dist/src/lib/billing/configure.d.ts +6 -0
  34. package/dist/src/lib/billing/configure.js +13 -0
  35. package/dist/src/lib/billing/customer-repair.d.ts +4 -0
  36. package/dist/src/lib/billing/customer-repair.js +19 -0
  37. package/dist/src/lib/billing/demo-seed.d.ts +1 -0
  38. package/dist/src/lib/billing/demo-seed.js +13 -0
  39. package/dist/src/lib/billing/entitlements.d.ts +3 -0
  40. package/dist/src/lib/billing/entitlements.js +16 -0
  41. package/dist/src/lib/billing/events.d.ts +12 -0
  42. package/dist/src/lib/billing/events.js +20 -0
  43. package/dist/src/lib/billing/plans.d.ts +9 -0
  44. package/dist/src/lib/billing/plans.js +41 -0
  45. package/dist/src/lib/billing/protection.d.ts +6 -0
  46. package/dist/src/lib/billing/protection.js +16 -0
  47. package/dist/src/lib/billing/refresh.d.ts +1 -0
  48. package/dist/src/lib/billing/refresh.js +32 -0
  49. package/dist/src/lib/billing/subscriptions.d.ts +16 -0
  50. package/dist/src/lib/billing/subscriptions.js +36 -0
  51. package/dist/src/lib/billing/teams.d.ts +42 -0
  52. package/dist/src/lib/billing/teams.js +104 -0
  53. package/dist/src/lib/billing/usage.d.ts +17 -0
  54. package/dist/src/lib/billing/usage.js +40 -0
  55. package/dist/src/lib/billing/webhook-sync.d.ts +26 -0
  56. package/dist/src/lib/billing/webhook-sync.js +39 -0
  57. package/dist/src/lib/paddle/api.d.ts +1 -0
  58. package/dist/src/lib/paddle/api.js +21 -0
  59. package/dist/src/lib/paddle/client.d.ts +1 -0
  60. package/dist/src/lib/paddle/client.js +13 -0
  61. package/dist/src/lib/paddle/customers.d.ts +15 -0
  62. package/dist/src/lib/paddle/customers.js +14 -0
  63. package/dist/src/lib/paddle/events.d.ts +10 -0
  64. package/dist/src/lib/paddle/events.js +11 -0
  65. package/dist/src/lib/paddle/hooks.d.ts +4 -0
  66. package/dist/src/lib/paddle/hooks.js +11 -0
  67. package/dist/src/lib/paddle/portal.d.ts +8 -0
  68. package/dist/src/lib/paddle/portal.js +28 -0
  69. package/dist/src/lib/paddle/subscriptions.d.ts +20 -0
  70. package/dist/src/lib/paddle/subscriptions.js +10 -0
  71. package/dist/src/lib/paddle/types.d.ts +8 -0
  72. package/dist/src/lib/paddle/types.js +1 -0
  73. package/dist/src/lib/paddle/webhook.d.ts +1 -0
  74. package/dist/src/lib/paddle/webhook.js +8 -0
  75. package/dist/src/lib/utils.d.ts +2 -0
  76. package/dist/src/lib/utils.js +5 -0
  77. package/docs/agency-use-case.md +19 -0
  78. package/docs/quickstart.md +24 -0
  79. package/docs/recipes/prisma/adapter.example.txt +70 -0
  80. package/docs/recipes/prisma/client.example.txt +4 -0
  81. package/docs/recipes/prisma/schema.prisma +30 -0
  82. package/package.json +59 -0
  83. package/recipes/README.md +20 -0
  84. package/recipes/nextjs/app/billing.ts +14 -0
  85. package/recipes/nextjs/custom-adapter.ts +26 -0
  86. package/recipes/nextjs/protected-api-route.ts +27 -0
  87. package/recipes/nextjs/server-page-gate.tsx +18 -0
  88. package/recipes/nextjs/webhook-route.ts +23 -0
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
+ export function SubscriptionGate({ active, children, fallback, }) {
4
+ if (!active) {
5
+ return (fallback ?? (_jsx("div", { className: "rounded-xl border p-6", children: "Upgrade required." })));
6
+ }
7
+ return _jsx(_Fragment, { children: children });
8
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./types";
2
+ export * from "./memory";
3
+ export * from "./prisma";
@@ -0,0 +1,3 @@
1
+ export * from "./types";
2
+ export * from "./memory";
3
+ export * from "./prisma";
@@ -0,0 +1,2 @@
1
+ import type { BillingAdapter } from "./types";
2
+ export declare const memoryBillingAdapter: BillingAdapter;
@@ -0,0 +1,41 @@
1
+ const subscriptions = new Map();
2
+ const usage = new Map();
3
+ const teams = new Map();
4
+ const billingEvents = new Map();
5
+ function usageKey(userId, key, period) {
6
+ return `${userId}:${key}:${period}`;
7
+ }
8
+ export const memoryBillingAdapter = {
9
+ async getSubscription(userId) {
10
+ return subscriptions.get(userId) ?? null;
11
+ },
12
+ async upsertSubscription(record) {
13
+ subscriptions.set(record.userId, record);
14
+ return record;
15
+ },
16
+ async getUsage(userId, key, period) {
17
+ return (usage.get(usageKey(userId, key, period)) ?? 0);
18
+ },
19
+ async incrementUsage(userId, key, period, amount) {
20
+ const id = usageKey(userId, key, period);
21
+ const next = (usage.get(id) ?? 0) + amount;
22
+ usage.set(id, next);
23
+ return next;
24
+ },
25
+ async getTeam(teamId) {
26
+ return teams.get(teamId) ?? null;
27
+ },
28
+ async upsertTeam(team) {
29
+ teams.set(team.teamId, team);
30
+ return team;
31
+ },
32
+ async recordBillingEvent(event) {
33
+ const current = billingEvents.get(event.userId) ?? [];
34
+ current.unshift(event);
35
+ billingEvents.set(event.userId, current);
36
+ return event;
37
+ },
38
+ async getBillingEvents(userId) {
39
+ return (billingEvents.get(userId) ?? []);
40
+ },
41
+ };
@@ -0,0 +1,28 @@
1
+ import type { BillingAdapter } from "../types";
2
+ import type { SubscriptionRecord } from "../../subscriptions";
3
+ import type { TeamRecord } from "../../teams";
4
+ import type { BillingEvent } from "../../events";
5
+ type PrismaLike = {
6
+ subscription: {
7
+ findUnique(args: unknown): Promise<SubscriptionRecord | null>;
8
+ upsert(args: unknown): Promise<SubscriptionRecord>;
9
+ };
10
+ usageEvent: {
11
+ findUnique(args: unknown): Promise<{
12
+ count: number;
13
+ } | null>;
14
+ upsert(args: unknown): Promise<{
15
+ count: number;
16
+ }>;
17
+ };
18
+ team: {
19
+ findUnique(args: unknown): Promise<TeamRecord | null>;
20
+ upsert(args: unknown): Promise<TeamRecord>;
21
+ };
22
+ billingEvent: {
23
+ create(args: unknown): Promise<BillingEvent>;
24
+ findMany(args: unknown): Promise<BillingEvent[]>;
25
+ };
26
+ };
27
+ export declare function createPrismaBillingAdapter(prisma: PrismaLike): BillingAdapter;
28
+ export {};
@@ -0,0 +1,80 @@
1
+ export function createPrismaBillingAdapter(prisma) {
2
+ return {
3
+ async getSubscription(userId) {
4
+ return prisma.subscription.findUnique({
5
+ where: { userId },
6
+ });
7
+ },
8
+ async upsertSubscription(record) {
9
+ return prisma.subscription.upsert({
10
+ where: {
11
+ userId: record.userId,
12
+ },
13
+ create: record,
14
+ update: record,
15
+ });
16
+ },
17
+ async getUsage(userId, key, period) {
18
+ const usage = await prisma.usageEvent.findUnique({
19
+ where: {
20
+ userId_key_period: {
21
+ userId,
22
+ key,
23
+ period,
24
+ },
25
+ },
26
+ });
27
+ return usage?.count ?? 0;
28
+ },
29
+ async incrementUsage(userId, key, period, amount) {
30
+ const usage = await prisma.usageEvent.upsert({
31
+ where: {
32
+ userId_key_period: {
33
+ userId,
34
+ key,
35
+ period,
36
+ },
37
+ },
38
+ create: {
39
+ userId,
40
+ key,
41
+ period,
42
+ count: amount,
43
+ },
44
+ update: {
45
+ count: {
46
+ increment: amount,
47
+ },
48
+ },
49
+ });
50
+ return usage.count;
51
+ },
52
+ async getTeam(teamId) {
53
+ return prisma.team.findUnique({
54
+ where: { teamId },
55
+ });
56
+ },
57
+ async upsertTeam(team) {
58
+ return prisma.team.upsert({
59
+ where: {
60
+ teamId: team.teamId,
61
+ },
62
+ create: team,
63
+ update: team,
64
+ });
65
+ },
66
+ async recordBillingEvent(event) {
67
+ return prisma.billingEvent.create({
68
+ data: event,
69
+ });
70
+ },
71
+ async getBillingEvents(userId) {
72
+ return prisma.billingEvent.findMany({
73
+ where: { userId },
74
+ orderBy: {
75
+ createdAt: "desc",
76
+ },
77
+ });
78
+ },
79
+ };
80
+ }
@@ -0,0 +1,13 @@
1
+ import type { SubscriptionRecord } from "../subscriptions";
2
+ import type { TeamRecord } from "../teams";
3
+ import type { BillingEvent } from "../events";
4
+ export interface BillingAdapter {
5
+ getSubscription(userId: string): Promise<SubscriptionRecord | null>;
6
+ upsertSubscription(record: SubscriptionRecord): Promise<SubscriptionRecord>;
7
+ getUsage(userId: string, key: string, period: string): Promise<number>;
8
+ incrementUsage(userId: string, key: string, period: string, amount: number): Promise<number>;
9
+ getTeam?(teamId: string): Promise<TeamRecord | null>;
10
+ upsertTeam?(team: TeamRecord): Promise<TeamRecord>;
11
+ recordBillingEvent?(event: BillingEvent): Promise<BillingEvent>;
12
+ getBillingEvents?(userId: string): Promise<BillingEvent[]>;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { BillingAdapter } from "./adapters";
2
+ export declare function configureBilling({ adapter, }: {
3
+ adapter: BillingAdapter;
4
+ }): {
5
+ adapterConfigured: boolean;
6
+ };
@@ -0,0 +1,13 @@
1
+ import { setBillingAdapter } from "./subscriptions";
2
+ import { setUsageAdapter } from "./usage";
3
+ import { setTeamAdapter } from "./teams";
4
+ import { setBillingEventAdapter } from "./events";
5
+ export function configureBilling({ adapter, }) {
6
+ setBillingAdapter(adapter);
7
+ setUsageAdapter(adapter);
8
+ setTeamAdapter(adapter);
9
+ setBillingEventAdapter(adapter);
10
+ return {
11
+ adapterConfigured: true,
12
+ };
13
+ }
@@ -0,0 +1,4 @@
1
+ export declare function repairSubscriptionByEmail(email: string): Promise<{
2
+ customer: import("@/lib/paddle/customers").PaddleCustomer;
3
+ subscription: import("./subscriptions").SubscriptionRecord;
4
+ }>;
@@ -0,0 +1,19 @@
1
+ import { findFirstPaddleCustomerByEmail } from "@/lib/paddle/customers";
2
+ import { fetchPaddleSubscriptionsForCustomer } from "@/lib/paddle/subscriptions";
3
+ import { refreshSubscriptionFromPaddle } from "./refresh";
4
+ export async function repairSubscriptionByEmail(email) {
5
+ const customer = await findFirstPaddleCustomerByEmail(email);
6
+ if (!customer) {
7
+ throw new Error("No Paddle customer found for email");
8
+ }
9
+ const subscriptions = await fetchPaddleSubscriptionsForCustomer(customer.id);
10
+ const activeSubscription = subscriptions.data?.find((sub) => ["active", "trialing", "past_due"].includes(sub.status));
11
+ if (!activeSubscription) {
12
+ throw new Error("No active Paddle subscription found for customer");
13
+ }
14
+ const repaired = await refreshSubscriptionFromPaddle(activeSubscription.id);
15
+ return {
16
+ customer,
17
+ subscription: repaired,
18
+ };
19
+ }
@@ -0,0 +1 @@
1
+ export declare function seedDemoBilling(): Promise<void>;
@@ -0,0 +1,13 @@
1
+ import { upsertSubscription } from "./subscriptions";
2
+ let seeded = false;
3
+ export async function seedDemoBilling() {
4
+ if (seeded) {
5
+ return;
6
+ }
7
+ await upsertSubscription({
8
+ userId: "demo-user-1",
9
+ plan: "pro",
10
+ status: "active",
11
+ });
12
+ seeded = true;
13
+ }
@@ -0,0 +1,3 @@
1
+ import { type FeatureKey } from "./plans";
2
+ export declare function hasFeature(userId: string, feature: FeatureKey): Promise<boolean>;
3
+ export declare function requireFeature(userId: string, feature: FeatureKey): Promise<boolean>;
@@ -0,0 +1,16 @@
1
+ import { plans } from "./plans";
2
+ import { getSubscription, isSubscriptionActive } from "./subscriptions";
3
+ export async function hasFeature(userId, feature) {
4
+ const subscription = await getSubscription(userId);
5
+ if (!isSubscriptionActive(subscription)) {
6
+ return false;
7
+ }
8
+ return plans[subscription.plan].features.includes(feature);
9
+ }
10
+ export async function requireFeature(userId, feature) {
11
+ const allowed = await hasFeature(userId, feature);
12
+ if (!allowed) {
13
+ throw new Error(`Feature not available: ${feature}`);
14
+ }
15
+ return true;
16
+ }
@@ -0,0 +1,12 @@
1
+ import { type BillingAdapter } from "./adapters";
2
+ export type BillingEventType = "subscription_created" | "payment_succeeded" | "payment_failed" | "subscription_updated" | "subscription_paused" | "subscription_canceled" | "portal_opened" | "repair_performed";
3
+ export interface BillingEvent {
4
+ id: string;
5
+ userId: string;
6
+ type: BillingEventType;
7
+ createdAt: string;
8
+ metadata?: Record<string, unknown>;
9
+ }
10
+ export declare function setBillingEventAdapter(nextAdapter: BillingAdapter): void;
11
+ export declare function recordBillingEvent(event: BillingEvent): Promise<BillingEvent>;
12
+ export declare function getBillingEvents(userId: string): Promise<BillingEvent[]>;
@@ -0,0 +1,20 @@
1
+ import { memoryBillingAdapter, } from "./adapters";
2
+ let adapter = memoryBillingAdapter;
3
+ export function setBillingEventAdapter(nextAdapter) {
4
+ adapter = nextAdapter;
5
+ }
6
+ function requireEventAdapter() {
7
+ if (!adapter.recordBillingEvent ||
8
+ !adapter.getBillingEvents) {
9
+ throw new Error("Billing adapter does not support event operations");
10
+ }
11
+ return adapter;
12
+ }
13
+ export async function recordBillingEvent(event) {
14
+ return requireEventAdapter()
15
+ .recordBillingEvent(event);
16
+ }
17
+ export async function getBillingEvents(userId) {
18
+ return requireEventAdapter()
19
+ .getBillingEvents(userId);
20
+ }
@@ -0,0 +1,9 @@
1
+ export type PlanId = "free" | "starter" | "pro" | "business";
2
+ export type FeatureKey = "checkout" | "customer_portal" | "usage_limits" | "team_members" | "api_access" | "priority_support";
3
+ export declare const plans: Record<PlanId, {
4
+ name: string;
5
+ monthlyLimit: number;
6
+ seatLimit: number;
7
+ features: FeatureKey[];
8
+ }>;
9
+ export declare function getPlanSeatLimit(plan: PlanId): number;
@@ -0,0 +1,41 @@
1
+ export const plans = {
2
+ free: {
3
+ name: "Free",
4
+ monthlyLimit: 25,
5
+ seatLimit: 1,
6
+ features: ["checkout"],
7
+ },
8
+ starter: {
9
+ name: "Starter",
10
+ monthlyLimit: 500,
11
+ seatLimit: 1,
12
+ features: ["checkout", "customer_portal"],
13
+ },
14
+ pro: {
15
+ name: "Pro",
16
+ monthlyLimit: 5000,
17
+ seatLimit: 5,
18
+ features: [
19
+ "checkout",
20
+ "customer_portal",
21
+ "usage_limits",
22
+ "api_access",
23
+ ],
24
+ },
25
+ business: {
26
+ name: "Business",
27
+ monthlyLimit: 50000,
28
+ seatLimit: 25,
29
+ features: [
30
+ "checkout",
31
+ "customer_portal",
32
+ "usage_limits",
33
+ "team_members",
34
+ "api_access",
35
+ "priority_support",
36
+ ],
37
+ },
38
+ };
39
+ export function getPlanSeatLimit(plan) {
40
+ return plans[plan].seatLimit;
41
+ }
@@ -0,0 +1,6 @@
1
+ import type { PlanId, FeatureKey } from "./plans";
2
+ export declare function protectPlan(userId: string, plan: PlanId): Promise<import("./subscriptions").SubscriptionRecord>;
3
+ export declare function protectFeature(userId: string, feature: FeatureKey): Promise<boolean>;
4
+ export declare function protectUsage(userId: string, usageKey: string, amount?: number): Promise<{
5
+ allowed: boolean;
6
+ }>;
@@ -0,0 +1,16 @@
1
+ import { requireFeature } from "./entitlements";
2
+ import { requireSubscription } from "./subscriptions";
3
+ import { requireUsage, incrementUsage } from "./usage";
4
+ export async function protectPlan(userId, plan) {
5
+ return requireSubscription(userId, plan);
6
+ }
7
+ export async function protectFeature(userId, feature) {
8
+ return requireFeature(userId, feature);
9
+ }
10
+ export async function protectUsage(userId, usageKey, amount = 1) {
11
+ await requireUsage(userId, usageKey);
12
+ await incrementUsage(userId, usageKey, amount);
13
+ return {
14
+ allowed: true,
15
+ };
16
+ }
@@ -0,0 +1 @@
1
+ export declare function refreshSubscriptionFromPaddle(subscriptionId: string): Promise<import("./subscriptions").SubscriptionRecord>;
@@ -0,0 +1,32 @@
1
+ import { upsertSubscription } from "./subscriptions";
2
+ import { fetchPaddleSubscription } from "@/lib/paddle/subscriptions";
3
+ function mapStatus(status) {
4
+ if (status === "active")
5
+ return "active";
6
+ if (status === "trialing")
7
+ return "trialing";
8
+ if (status === "paused")
9
+ return "paused";
10
+ if (status === "canceled")
11
+ return "canceled";
12
+ if (status === "past_due")
13
+ return "past_due";
14
+ return "none";
15
+ }
16
+ export async function refreshSubscriptionFromPaddle(subscriptionId) {
17
+ const response = await fetchPaddleSubscription(subscriptionId);
18
+ const sub = response.data;
19
+ const userId = sub?.custom_data?.userId;
20
+ if (!sub || !userId) {
21
+ throw new Error("Paddle subscription missing custom_data.userId");
22
+ }
23
+ const plan = sub.custom_data?.plan ?? "starter";
24
+ return upsertSubscription({
25
+ userId,
26
+ plan: plan,
27
+ status: mapStatus(sub.status),
28
+ paddleCustomerId: sub.customer_id,
29
+ paddleSubscriptionId: sub.id,
30
+ currentPeriodEnd: sub.current_billing_period?.ends_at,
31
+ });
32
+ }
@@ -0,0 +1,16 @@
1
+ import type { PlanId } from "./plans";
2
+ import { type BillingAdapter } from "./adapters";
3
+ export type SubscriptionStatus = "active" | "trialing" | "past_due" | "paused" | "canceled" | "expired" | "none";
4
+ export interface SubscriptionRecord {
5
+ userId: string;
6
+ plan: PlanId;
7
+ status: SubscriptionStatus;
8
+ paddleCustomerId?: string;
9
+ paddleSubscriptionId?: string;
10
+ currentPeriodEnd?: string;
11
+ }
12
+ export declare function setBillingAdapter(nextAdapter: BillingAdapter): void;
13
+ export declare function getSubscription(userId: string): Promise<SubscriptionRecord>;
14
+ export declare function upsertSubscription(record: SubscriptionRecord): Promise<SubscriptionRecord>;
15
+ export declare function isSubscriptionActive(subscription: SubscriptionRecord): boolean;
16
+ export declare function requireSubscription(userId: string, minimumPlan?: PlanId): Promise<SubscriptionRecord>;
@@ -0,0 +1,36 @@
1
+ import { memoryBillingAdapter, } from "./adapters";
2
+ let adapter = memoryBillingAdapter;
3
+ export function setBillingAdapter(nextAdapter) {
4
+ adapter = nextAdapter;
5
+ }
6
+ export async function getSubscription(userId) {
7
+ return ((await adapter.getSubscription(userId)) ?? {
8
+ userId,
9
+ plan: "free",
10
+ status: "none",
11
+ });
12
+ }
13
+ export async function upsertSubscription(record) {
14
+ return adapter.upsertSubscription(record);
15
+ }
16
+ export function isSubscriptionActive(subscription) {
17
+ return (subscription.status === "active" ||
18
+ subscription.status === "trialing");
19
+ }
20
+ export async function requireSubscription(userId, minimumPlan = "starter") {
21
+ const subscription = await getSubscription(userId);
22
+ if (!isSubscriptionActive(subscription)) {
23
+ throw new Error("Active subscription required");
24
+ }
25
+ const order = [
26
+ "free",
27
+ "starter",
28
+ "pro",
29
+ "business",
30
+ ];
31
+ if (order.indexOf(subscription.plan) <
32
+ order.indexOf(minimumPlan)) {
33
+ throw new Error(`${minimumPlan} plan required`);
34
+ }
35
+ return subscription;
36
+ }
@@ -0,0 +1,42 @@
1
+ import { type BillingAdapter } from "./adapters";
2
+ import { type PlanId } from "./plans";
3
+ export type TeamRole = "owner" | "admin" | "member";
4
+ export interface TeamMember {
5
+ userId: string;
6
+ email?: string;
7
+ role: TeamRole;
8
+ joinedAt: string;
9
+ }
10
+ export interface TeamRecord {
11
+ teamId: string;
12
+ name: string;
13
+ plan: PlanId;
14
+ ownerId: string;
15
+ members: TeamMember[];
16
+ }
17
+ export declare function setTeamAdapter(nextAdapter: BillingAdapter): void;
18
+ export declare function createTeam({ teamId, name, ownerId, plan, }: {
19
+ teamId: string;
20
+ name: string;
21
+ ownerId: string;
22
+ plan?: PlanId;
23
+ }): Promise<TeamRecord>;
24
+ export declare function getTeam(teamId: string): Promise<TeamRecord | null>;
25
+ export declare function updateTeamPlan(teamId: string, plan: PlanId): Promise<TeamRecord>;
26
+ export declare function getSeatUsage(teamId: string): Promise<{
27
+ used: number;
28
+ limit: number;
29
+ remaining: number;
30
+ allowed: boolean;
31
+ }>;
32
+ export declare function canInviteTeamMember(teamId: string): Promise<boolean>;
33
+ export declare function addTeamMember({ teamId, userId, email, role, }: {
34
+ teamId: string;
35
+ userId: string;
36
+ email?: string;
37
+ role?: TeamRole;
38
+ }): Promise<TeamRecord>;
39
+ export declare function removeTeamMember({ teamId, userId, }: {
40
+ teamId: string;
41
+ userId: string;
42
+ }): Promise<TeamRecord>;
@@ -0,0 +1,104 @@
1
+ import { memoryBillingAdapter, } from "./adapters";
2
+ import { getPlanSeatLimit } from "./plans";
3
+ let adapter = memoryBillingAdapter;
4
+ export function setTeamAdapter(nextAdapter) {
5
+ adapter = nextAdapter;
6
+ }
7
+ function requireTeamAdapter() {
8
+ if (!adapter.getTeam || !adapter.upsertTeam) {
9
+ throw new Error("Billing adapter does not support team operations");
10
+ }
11
+ return adapter;
12
+ }
13
+ export async function createTeam({ teamId, name, ownerId, plan = "free", }) {
14
+ const now = new Date().toISOString();
15
+ const team = {
16
+ teamId,
17
+ name,
18
+ ownerId,
19
+ plan,
20
+ members: [
21
+ {
22
+ userId: ownerId,
23
+ role: "owner",
24
+ joinedAt: now,
25
+ },
26
+ ],
27
+ };
28
+ return requireTeamAdapter()
29
+ .upsertTeam(team);
30
+ }
31
+ export async function getTeam(teamId) {
32
+ return requireTeamAdapter()
33
+ .getTeam(teamId);
34
+ }
35
+ export async function updateTeamPlan(teamId, plan) {
36
+ const team = await getTeam(teamId);
37
+ if (!team) {
38
+ throw new Error("Team not found");
39
+ }
40
+ team.plan = plan;
41
+ return requireTeamAdapter()
42
+ .upsertTeam(team);
43
+ }
44
+ export async function getSeatUsage(teamId) {
45
+ const team = await getTeam(teamId);
46
+ if (!team) {
47
+ throw new Error("Team not found");
48
+ }
49
+ const limit = getPlanSeatLimit(team.plan);
50
+ const used = team.members.length;
51
+ return {
52
+ used,
53
+ limit,
54
+ remaining: Math.max(0, limit - used),
55
+ allowed: used < limit,
56
+ };
57
+ }
58
+ export async function canInviteTeamMember(teamId) {
59
+ const usage = await getSeatUsage(teamId);
60
+ return usage.allowed;
61
+ }
62
+ export async function addTeamMember({ teamId, userId, email, role = "member", }) {
63
+ const team = await getTeam(teamId);
64
+ if (!team) {
65
+ throw new Error("Team not found");
66
+ }
67
+ const alreadyMember = team.members.some((member) => member.userId === userId);
68
+ if (alreadyMember) {
69
+ return team;
70
+ }
71
+ const usage = await getSeatUsage(teamId);
72
+ if (!usage.allowed) {
73
+ throw new Error("Seat limit reached");
74
+ }
75
+ const nextTeam = {
76
+ ...team,
77
+ members: [
78
+ ...team.members,
79
+ {
80
+ userId,
81
+ email,
82
+ role,
83
+ joinedAt: new Date().toISOString(),
84
+ },
85
+ ],
86
+ };
87
+ return requireTeamAdapter()
88
+ .upsertTeam(nextTeam);
89
+ }
90
+ export async function removeTeamMember({ teamId, userId, }) {
91
+ const team = await getTeam(teamId);
92
+ if (!team) {
93
+ throw new Error("Team not found");
94
+ }
95
+ if (team.ownerId === userId) {
96
+ throw new Error("Cannot remove team owner");
97
+ }
98
+ const nextTeam = {
99
+ ...team,
100
+ members: team.members.filter((member) => member.userId !== userId),
101
+ };
102
+ return requireTeamAdapter()
103
+ .upsertTeam(nextTeam);
104
+ }
@@ -0,0 +1,17 @@
1
+ import { type BillingAdapter } from "./adapters";
2
+ export declare function setUsageAdapter(nextAdapter: BillingAdapter): void;
3
+ export declare function getUsage(userId: string, key: string): Promise<number>;
4
+ export declare function getUsageLimit(userId: string): Promise<number>;
5
+ export declare function canUse(userId: string, key: string): Promise<{
6
+ allowed: boolean;
7
+ used: number;
8
+ limit: number;
9
+ remaining: number;
10
+ }>;
11
+ export declare function incrementUsage(userId: string, key: string, amount?: number): Promise<number>;
12
+ export declare function requireUsage(userId: string, key: string): Promise<{
13
+ allowed: boolean;
14
+ used: number;
15
+ limit: number;
16
+ remaining: number;
17
+ }>;