@commet/next 0.5.7 → 1.0.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.
- package/dist/index.d.mts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +29 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +29 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -72,7 +72,7 @@ interface WebhooksConfig {
|
|
|
72
72
|
* onSubscriptionActivated: async (payload) => {
|
|
73
73
|
* await db.update(users)
|
|
74
74
|
* .set({ isPaid: true })
|
|
75
|
-
* .where(eq(users.id, payload.data.
|
|
75
|
+
* .where(eq(users.id, payload.data.customerId));
|
|
76
76
|
* }
|
|
77
77
|
* ```
|
|
78
78
|
*/
|
|
@@ -90,7 +90,7 @@ interface WebhooksConfig {
|
|
|
90
90
|
* onSubscriptionCanceled: async (payload) => {
|
|
91
91
|
* await db.update(users)
|
|
92
92
|
* .set({ isPaid: false })
|
|
93
|
-
* .where(eq(users.id, payload.data.
|
|
93
|
+
* .where(eq(users.id, payload.data.customerId));
|
|
94
94
|
* }
|
|
95
95
|
* ```
|
|
96
96
|
*/
|
|
@@ -129,6 +129,49 @@ interface WebhooksConfig {
|
|
|
129
129
|
* ```
|
|
130
130
|
*/
|
|
131
131
|
onSubscriptionUpdated?: (payload: WebhookPayload) => Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Handles the `subscription.plan_changed` webhook event
|
|
134
|
+
*
|
|
135
|
+
* Fired when a subscription changes from one plan to another (upgrade, downgrade,
|
|
136
|
+
* or billing interval change). The subscription stays active — access does not change.
|
|
137
|
+
*
|
|
138
|
+
* @param payload - The webhook payload containing plan change data
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* onSubscriptionPlanChanged: async (payload) => {
|
|
143
|
+
* await db.update(users)
|
|
144
|
+
* .set({ planId: payload.data.currentPlan.id })
|
|
145
|
+
* .where(eq(users.id, payload.data.customerId));
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* Handles the `payment.received` webhook event
|
|
152
|
+
*
|
|
153
|
+
* Fired when a payment is successfully processed for a subscription.
|
|
154
|
+
*
|
|
155
|
+
* @param payload - The webhook payload containing payment data
|
|
156
|
+
*/
|
|
157
|
+
onPaymentReceived?: (payload: WebhookPayload) => Promise<void>;
|
|
158
|
+
/**
|
|
159
|
+
* Handles the `payment.failed` webhook event
|
|
160
|
+
*
|
|
161
|
+
* Fired when a payment attempt fails. Use this to notify users
|
|
162
|
+
* or trigger dunning flows.
|
|
163
|
+
*
|
|
164
|
+
* @param payload - The webhook payload containing payment data
|
|
165
|
+
*/
|
|
166
|
+
onPaymentFailed?: (payload: WebhookPayload) => Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Handles the `invoice.created` webhook event
|
|
169
|
+
*
|
|
170
|
+
* Fired when a new invoice is generated for a subscription billing cycle.
|
|
171
|
+
*
|
|
172
|
+
* @param payload - The webhook payload containing invoice data
|
|
173
|
+
*/
|
|
174
|
+
onInvoiceCreated?: (payload: WebhookPayload) => Promise<void>;
|
|
132
175
|
/**
|
|
133
176
|
* Catch-all handler that receives all webhook events
|
|
134
177
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ interface WebhooksConfig {
|
|
|
72
72
|
* onSubscriptionActivated: async (payload) => {
|
|
73
73
|
* await db.update(users)
|
|
74
74
|
* .set({ isPaid: true })
|
|
75
|
-
* .where(eq(users.id, payload.data.
|
|
75
|
+
* .where(eq(users.id, payload.data.customerId));
|
|
76
76
|
* }
|
|
77
77
|
* ```
|
|
78
78
|
*/
|
|
@@ -90,7 +90,7 @@ interface WebhooksConfig {
|
|
|
90
90
|
* onSubscriptionCanceled: async (payload) => {
|
|
91
91
|
* await db.update(users)
|
|
92
92
|
* .set({ isPaid: false })
|
|
93
|
-
* .where(eq(users.id, payload.data.
|
|
93
|
+
* .where(eq(users.id, payload.data.customerId));
|
|
94
94
|
* }
|
|
95
95
|
* ```
|
|
96
96
|
*/
|
|
@@ -129,6 +129,49 @@ interface WebhooksConfig {
|
|
|
129
129
|
* ```
|
|
130
130
|
*/
|
|
131
131
|
onSubscriptionUpdated?: (payload: WebhookPayload) => Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Handles the `subscription.plan_changed` webhook event
|
|
134
|
+
*
|
|
135
|
+
* Fired when a subscription changes from one plan to another (upgrade, downgrade,
|
|
136
|
+
* or billing interval change). The subscription stays active — access does not change.
|
|
137
|
+
*
|
|
138
|
+
* @param payload - The webhook payload containing plan change data
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* onSubscriptionPlanChanged: async (payload) => {
|
|
143
|
+
* await db.update(users)
|
|
144
|
+
* .set({ planId: payload.data.currentPlan.id })
|
|
145
|
+
* .where(eq(users.id, payload.data.customerId));
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* Handles the `payment.received` webhook event
|
|
152
|
+
*
|
|
153
|
+
* Fired when a payment is successfully processed for a subscription.
|
|
154
|
+
*
|
|
155
|
+
* @param payload - The webhook payload containing payment data
|
|
156
|
+
*/
|
|
157
|
+
onPaymentReceived?: (payload: WebhookPayload) => Promise<void>;
|
|
158
|
+
/**
|
|
159
|
+
* Handles the `payment.failed` webhook event
|
|
160
|
+
*
|
|
161
|
+
* Fired when a payment attempt fails. Use this to notify users
|
|
162
|
+
* or trigger dunning flows.
|
|
163
|
+
*
|
|
164
|
+
* @param payload - The webhook payload containing payment data
|
|
165
|
+
*/
|
|
166
|
+
onPaymentFailed?: (payload: WebhookPayload) => Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Handles the `invoice.created` webhook event
|
|
169
|
+
*
|
|
170
|
+
* Fired when a new invoice is generated for a subscription billing cycle.
|
|
171
|
+
*
|
|
172
|
+
* @param payload - The webhook payload containing invoice data
|
|
173
|
+
*/
|
|
174
|
+
onInvoiceCreated?: (payload: WebhookPayload) => Promise<void>;
|
|
132
175
|
/**
|
|
133
176
|
* Catch-all handler that receives all webhook events
|
|
134
177
|
*
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,9 @@ var CustomerPortal = ({
|
|
|
51
51
|
customerId
|
|
52
52
|
});
|
|
53
53
|
if (!result.success || !result.data) {
|
|
54
|
-
throw new Error(
|
|
54
|
+
throw new Error(
|
|
55
|
+
result.error?.message || "Failed to create portal session"
|
|
56
|
+
);
|
|
55
57
|
}
|
|
56
58
|
return import_server.NextResponse.redirect(result.data.portalUrl);
|
|
57
59
|
} catch (error) {
|
|
@@ -90,7 +92,7 @@ var PricingMarkdown = ({
|
|
|
90
92
|
try {
|
|
91
93
|
const plansResult = await commet.plans.list();
|
|
92
94
|
if (!plansResult.success || !plansResult.data) {
|
|
93
|
-
throw new Error(plansResult.message ?? "Failed to fetch plans");
|
|
95
|
+
throw new Error(plansResult.error?.message ?? "Failed to fetch plans");
|
|
94
96
|
}
|
|
95
97
|
const publicPlans = plansResult.data.filter((plan) => plan.isPublic);
|
|
96
98
|
const creditPacks = includeCreditPacks ? await commet.creditPacks.list().then(
|
|
@@ -313,6 +315,10 @@ var Webhooks = (config) => {
|
|
|
313
315
|
onSubscriptionCanceled,
|
|
314
316
|
onSubscriptionCreated,
|
|
315
317
|
onSubscriptionUpdated,
|
|
318
|
+
onSubscriptionPlanChanged,
|
|
319
|
+
onPaymentReceived,
|
|
320
|
+
onPaymentFailed,
|
|
321
|
+
onInvoiceCreated,
|
|
316
322
|
onPayload,
|
|
317
323
|
onError
|
|
318
324
|
} = config;
|
|
@@ -374,6 +380,26 @@ var Webhooks = (config) => {
|
|
|
374
380
|
promises.push(onSubscriptionUpdated(payload));
|
|
375
381
|
}
|
|
376
382
|
break;
|
|
383
|
+
case "subscription.plan_changed":
|
|
384
|
+
if (onSubscriptionPlanChanged) {
|
|
385
|
+
promises.push(onSubscriptionPlanChanged(payload));
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
case "payment.received":
|
|
389
|
+
if (onPaymentReceived) {
|
|
390
|
+
promises.push(onPaymentReceived(payload));
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
case "payment.failed":
|
|
394
|
+
if (onPaymentFailed) {
|
|
395
|
+
promises.push(onPaymentFailed(payload));
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
case "invoice.created":
|
|
399
|
+
if (onInvoiceCreated) {
|
|
400
|
+
promises.push(onInvoiceCreated(payload));
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
377
403
|
default:
|
|
378
404
|
console.log(`[Commet Webhook] Unhandled event: ${payload.event}`);
|
|
379
405
|
}
|
|
@@ -420,7 +446,7 @@ var Webhooks = (config) => {
|
|
|
420
446
|
};
|
|
421
447
|
|
|
422
448
|
// src/index.ts
|
|
423
|
-
(0, import_node4.registerIntegration)("@commet/next", "0.
|
|
449
|
+
(0, import_node4.registerIntegration)("@commet/next", "1.0.0");
|
|
424
450
|
// Annotate the CommonJS export names for ESM import in node:
|
|
425
451
|
0 && (module.exports = {
|
|
426
452
|
CustomerPortal,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/portal.ts","../src/pricing-markdown.ts","../src/webhooks.ts"],"sourcesContent":["/**\n * @commet/next - Next.js integration for Commet\n *\n * @example\n * Webhooks\n * ```typescript\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Handle activation\n * },\n * });\n * ```\n *\n * @example\n * Customer Portal\n * ```typescript\n * import { CustomerPortal } from \"@commet/next\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { registerIntegration } from \"@commet/node\";\n\ndeclare const __PKG_VERSION__: string;\nregisterIntegration(\"@commet/next\", __PKG_VERSION__);\n\nexport type { CustomerPortalConfig } from \"./portal\";\nexport { CustomerPortal } from \"./portal\";\nexport type { PricingMarkdownConfig } from \"./pricing-markdown\";\nexport { PricingMarkdown } from \"./pricing-markdown\";\nexport type {\n WebhookData,\n WebhookEvent,\n WebhookPayload,\n WebhooksConfig,\n} from \"./types\";\nexport { Webhooks } from \"./webhooks\";\n","import { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface CustomerPortalConfig {\n apiKey: string;\n getCustomerId: (req: NextRequest) => Promise<string | null>;\n onError?: (error: Error) => void;\n}\n\n/**\n * Creates Next.js route handler for Customer Portal access\n *\n * @example\n * ```typescript\n * // app/api/commet/portal/route.ts\n * import { CustomerPortal } from \"@commet/next\";\n * import { auth } from \"@/lib/auth\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id ?? null;\n * },\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Use in component\n * export function BillingButton() {\n * return (\n * <Button asChild>\n * <Link href=\"/api/commet/portal\">Manage Billing</Link>\n * </Button>\n * );\n * }\n * ```\n */\nexport const CustomerPortal = ({\n apiKey,\n getCustomerId,\n onError,\n}: CustomerPortalConfig) => {\n const commet = new Commet({\n apiKey,\n });\n\n return async (req: NextRequest) => {\n try {\n // Get customer identifier from request\n const customerId = await getCustomerId(req);\n\n if (!customerId) {\n return NextResponse.json(\n { error: \"Customer not authenticated\" },\n { status: 401 },\n );\n }\n\n // Get portal URL from Commet\n const result = await commet.portal.getUrl({\n customerId,\n });\n\n if (!result.success || !result.data) {\n throw new Error(result.message || \"Failed to create portal session\");\n }\n\n // Redirect to customer portal\n return NextResponse.redirect(result.data.portalUrl);\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n // Call custom error handler if provided\n onError?.(errorObj);\n\n // Log error\n console.error(\"[Commet Portal]\", errorObj);\n\n // Return error response\n return NextResponse.json(\n { error: \"Failed to access customer portal\" },\n { status: 500 },\n );\n }\n };\n};\n","import type {\n BillingInterval,\n CreditPack,\n Plan,\n PlanFeature,\n} from \"@commet/node\";\nimport { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface PricingMarkdownConfig {\n apiKey: string;\n title: string;\n description?: string;\n includeCreditPacks?: boolean;\n billingIntervals?: BillingInterval[];\n cacheMaxAge?: number;\n onError?: (error: Error) => void;\n}\n\nconst INTERVAL_SHORT: Record<BillingInterval, string> = {\n weekly: \"/wk\",\n monthly: \"/mo\",\n quarterly: \"/qtr\",\n yearly: \"/yr\",\n one_time: \"\",\n};\n\nexport const PricingMarkdown = ({\n apiKey,\n title,\n description,\n includeCreditPacks = false,\n billingIntervals,\n cacheMaxAge = 3600,\n onError,\n}: PricingMarkdownConfig) => {\n const commet = new Commet({ apiKey });\n\n return async (_req: NextRequest) => {\n try {\n const plansResult = await commet.plans.list();\n\n if (!plansResult.success || !plansResult.data) {\n throw new Error(plansResult.message ?? \"Failed to fetch plans\");\n }\n\n const publicPlans = plansResult.data.filter((plan) => plan.isPublic);\n\n const creditPacks = includeCreditPacks\n ? await commet.creditPacks\n .list()\n .then((result) =>\n result.success && result.data ? result.data : [],\n )\n : [];\n\n const markdown = generateMarkdown(\n title,\n description,\n publicPlans,\n creditPacks,\n billingIntervals,\n );\n\n return new NextResponse(markdown, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/markdown; charset=utf-8\",\n \"Cache-Control\": `public, s-maxage=${cacheMaxAge}, stale-while-revalidate=${cacheMaxAge * 2}`,\n },\n });\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n onError?.(errorObj);\n console.error(\"[Commet PricingMarkdown]\", errorObj);\n\n return NextResponse.json(\n { error: \"Failed to generate pricing markdown\" },\n { status: 500 },\n );\n }\n };\n};\n\nfunction generateMarkdown(\n title: string,\n description: string | undefined,\n plans: Plan[],\n creditPacks: CreditPack[],\n billingIntervals?: BillingInterval[],\n): string {\n const sections: string[] = [`# ${title}`];\n\n if (description) {\n sections.push(description);\n }\n\n if (plans.length > 0) {\n sections.push(renderPlansTable(plans, billingIntervals));\n\n const featuresSection = renderFeatures(plans);\n if (featuresSection) {\n sections.push(featuresSection);\n }\n }\n\n if (creditPacks.length > 0) {\n sections.push(renderCreditPacks(creditPacks));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nfunction renderPlansTable(\n plans: Plan[],\n filterIntervals?: BillingInterval[],\n): string {\n const usageFeatures = collectUsageFeatures(plans);\n\n const headerCols = [\"Plan\", \"Price\"];\n for (const { name, unitName } of usageFeatures) {\n headerCols.push(`${name}/${unitName ?? \"unit\"}`);\n headerCols.push(\"Overage\");\n }\n\n const rows = plans.map((plan) => {\n const priceCell = renderPriceCell(plan, filterIntervals);\n const cols = [plan.name, priceCell];\n\n const featuresByCode = new Map(\n plan.features.map((feature) => [feature.code, feature]),\n );\n\n for (const { code } of usageFeatures) {\n const feature = featuresByCode.get(code);\n\n if (!feature) {\n cols.push(\"—\", \"—\");\n continue;\n }\n\n if (feature.unlimited) {\n cols.push(\"Unlimited\", \"—\");\n continue;\n }\n\n const included = feature.includedAmount;\n cols.push(included ? formatNumber(included) : \"—\");\n\n const overagePrice =\n feature.overageEnabled && feature.overageUnitPrice !== undefined\n ? formatRatePrice(feature.overageUnitPrice)\n : \"—\";\n cols.push(overagePrice);\n }\n\n return cols;\n });\n\n const separator = headerCols.map(() => \"---\");\n const lines = [\n `| ${headerCols.join(\" | \")} |`,\n `| ${separator.join(\" | \")} |`,\n ...rows.map((cols) => `| ${cols.join(\" | \")} |`),\n ];\n\n const footnotes: string[] = [];\n\n const trialPlans = plans.filter((plan) =>\n plan.prices.some((price) => price.trialDays > 0),\n );\n for (const plan of trialPlans) {\n const trialPrice = plan.prices.find((price) => price.trialDays > 0)!;\n footnotes.push(\n `*${plan.name} plan includes a ${trialPrice.trialDays}-day free trial.*`,\n );\n }\n\n if (usageFeatures.length > 0) {\n footnotes.push(\n \"The overage rate applies only to usage beyond the included volume.\",\n );\n }\n\n return footnotes.length > 0\n ? `## Plans\\n\\n${lines.join(\"\\n\")}\\n\\n${footnotes.join(\"\\n\\n\")}`\n : `## Plans\\n\\n${lines.join(\"\\n\")}`;\n}\n\nfunction renderPriceCell(\n plan: Plan,\n filterIntervals?: BillingInterval[],\n): string {\n if (plan.isFree) return \"Free\";\n\n const prices = filterIntervals\n ? plan.prices.filter((price) =>\n filterIntervals.includes(price.billingInterval),\n )\n : plan.prices;\n\n if (prices.length === 0) return \"—\";\n\n return prices\n .map((price) => {\n if (price.price === 0) return \"Free\";\n return `${formatSettlementPrice(price.price)}${INTERVAL_SHORT[price.billingInterval]}`;\n })\n .join(\" / \");\n}\n\nfunction renderFeatures(plans: Plan[]): string | null {\n const allBooleanCodes = new Set<string>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (feature.type === \"boolean\") {\n allBooleanCodes.add(feature.code);\n }\n }\n }\n\n if (allBooleanCodes.size === 0) return null;\n\n const enabledOnAll = new Set<string>();\n for (const code of allBooleanCodes) {\n const enabledOnEveryPlan = plans.every((plan) => {\n const feature = plan.features.find((f) => f.code === code);\n return feature?.enabled;\n });\n if (enabledOnEveryPlan) {\n enabledOnAll.add(code);\n }\n }\n\n const perPlanLines: string[] = [];\n for (const plan of plans) {\n const uniqueFeatures = plan.features\n .filter(\n (feature) =>\n feature.type === \"boolean\" &&\n feature.enabled &&\n !enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n if (uniqueFeatures.length > 0) {\n perPlanLines.push(`- **${plan.name}**: ${uniqueFeatures.join(\", \")}`);\n }\n }\n\n const commonFeatures = plans[0]?.features\n .filter(\n (feature) => feature.type === \"boolean\" && enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n const parts: string[] = [];\n\n if (perPlanLines.length > 0) {\n parts.push(perPlanLines.join(\"\\n\"));\n }\n\n if (commonFeatures && commonFeatures.length > 0) {\n parts.push(`All plans include: ${commonFeatures.join(\", \")}.`);\n }\n\n return parts.length > 0 ? `## Features\\n\\n${parts.join(\"\\n\\n\")}` : null;\n}\n\nfunction renderCreditPacks(packs: CreditPack[]): string {\n const lines = [\n \"| Pack | Credits | Price |\",\n \"| --- | --- | --- |\",\n ...packs.map(\n (pack) =>\n `| ${pack.name} | ${formatNumber(pack.credits)} credits | ${formatSettlementPrice(pack.price)} |`,\n ),\n ];\n\n return `## Credit Packs\\n\\n${lines.join(\"\\n\")}`;\n}\n\ninterface UsageFeatureColumn {\n code: string;\n name: string;\n unitName: string | null;\n}\n\nfunction collectUsageFeatures(plans: Plan[]): UsageFeatureColumn[] {\n const seen = new Map<string, UsageFeatureColumn>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (\n (feature.type === \"usage\" || feature.type === \"seats\") &&\n !seen.has(feature.code) &&\n hasUsageData(feature)\n ) {\n seen.set(feature.code, {\n code: feature.code,\n name: feature.name,\n unitName: feature.unitName,\n });\n }\n }\n }\n return Array.from(seen.values());\n}\n\nfunction hasUsageData(feature: PlanFeature): boolean {\n return (\n feature.unlimited === true ||\n (feature.includedAmount !== undefined && feature.includedAmount > 0) ||\n (feature.overageEnabled === true && feature.overageUnitPrice !== undefined)\n );\n}\n\nfunction formatSettlementPrice(cents: number): string {\n if (cents === 0) return \"Free\";\n const dollars = cents / 100;\n const [whole, decimal] = dollars.toFixed(2).split(\".\");\n return `$${Number(whole).toLocaleString(\"en-US\")}.${decimal}`;\n}\n\nfunction formatRatePrice(rate: number): string {\n const dollars = rate / 10000;\n\n if (dollars === Math.floor(dollars)) {\n return `$${dollars.toFixed(2)}`;\n }\n\n const formatted = dollars.toFixed(4).replace(/0+$/, \"\");\n const decimalPlaces = formatted.split(\".\")[1]?.length ?? 0;\n if (decimalPlaces < 2) {\n return `$${dollars.toFixed(2)}`;\n }\n\n return `$${formatted}`;\n}\n\nfunction formatNumber(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n","import type { WebhookPayload } from \"@commet/node\";\nimport { Webhooks as CommetWebhooks } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\nimport type { WebhooksConfig } from \"./types\";\n\n/**\n * Create a Next.js webhook handler for Commet events\n *\n * Automatically verifies signatures, routes events to handlers, and returns proper responses.\n *\n * @param config - Webhook configuration with secret and event handlers\n * @returns Next.js route handler function\n *\n * @example\n * ```typescript\n * // app/api/webhooks/commet/route.ts\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Grant access to user\n * },\n * onSubscriptionCanceled: async (payload) => {\n * // Revoke access from user\n * },\n * });\n * ```\n */\nexport const Webhooks = (config: WebhooksConfig) => {\n const {\n webhookSecret,\n onSubscriptionActivated,\n onSubscriptionCanceled,\n onSubscriptionCreated,\n onSubscriptionUpdated,\n onPayload,\n onError,\n } = config;\n\n // Create webhook verifier instance\n const webhooks = new CommetWebhooks();\n\n return async (request: NextRequest) => {\n try {\n // 1. Read raw request body\n const rawBody = await request.text();\n\n // 2. Extract signature from headers\n const signature = request.headers.get(\"x-commet-signature\");\n\n // 3. Verify signature\n const isValid = webhooks.verify({\n payload: rawBody,\n signature,\n secret: webhookSecret,\n });\n\n if (!isValid) {\n console.error(\"[Commet Webhook] Invalid signature\");\n return NextResponse.json(\n { received: false, error: \"Invalid signature\" },\n { status: 403 },\n );\n }\n\n // 4. Parse payload\n let payload: WebhookPayload;\n try {\n payload = JSON.parse(rawBody) as WebhookPayload;\n } catch (parseError) {\n console.error(\"[Commet Webhook] Failed to parse payload:\", parseError);\n if (onError) {\n await onError(\n parseError instanceof Error\n ? parseError\n : new Error(\"Failed to parse webhook payload\"),\n rawBody,\n );\n }\n return NextResponse.json(\n { received: false, error: \"Invalid payload\" },\n { status: 400 },\n );\n }\n\n // 5. Collect promises for parallel execution\n const promises: Promise<void>[] = [];\n\n // Call catch-all handler if provided\n if (onPayload) {\n promises.push(onPayload(payload));\n }\n\n // 6. Route to specific event handler\n switch (payload.event) {\n case \"subscription.activated\":\n if (onSubscriptionActivated) {\n promises.push(onSubscriptionActivated(payload));\n }\n break;\n\n case \"subscription.canceled\":\n if (onSubscriptionCanceled) {\n promises.push(onSubscriptionCanceled(payload));\n }\n break;\n\n case \"subscription.created\":\n if (onSubscriptionCreated) {\n promises.push(onSubscriptionCreated(payload));\n }\n break;\n\n case \"subscription.updated\":\n if (onSubscriptionUpdated) {\n promises.push(onSubscriptionUpdated(payload));\n }\n break;\n\n default:\n console.log(`[Commet Webhook] Unhandled event: ${payload.event}`);\n }\n\n // 7. Execute all handlers in parallel\n try {\n await Promise.all(promises);\n } catch (handlerError) {\n console.error(\n \"[Commet Webhook] Handler error:\",\n handlerError instanceof Error ? handlerError.message : handlerError,\n );\n if (onError) {\n await onError(\n handlerError instanceof Error\n ? handlerError\n : new Error(\"Handler execution failed\"),\n payload,\n );\n }\n // Still return 200 to prevent retries for handler errors\n return NextResponse.json(\n { received: true, warning: \"Handler failed\" },\n { status: 200 },\n );\n }\n\n // 8. Success response\n return NextResponse.json({ received: true }, { status: 200 });\n } catch (error) {\n console.error(\"[Commet Webhook] Unexpected error:\", error);\n if (onError) {\n try {\n await onError(\n error instanceof Error ? error : new Error(\"Unexpected error\"),\n undefined,\n );\n } catch (errorHandlerError) {\n console.error(\n \"[Commet Webhook] Error handler failed:\",\n errorHandlerError,\n );\n }\n }\n return NextResponse.json(\n { received: false, error: \"Internal error\" },\n { status: 500 },\n );\n }\n };\n};\n"],"mappings":"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","names":["import_node","import_node","import_server","import_node","import_server","CommetWebhooks"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/portal.ts","../src/pricing-markdown.ts","../src/webhooks.ts"],"sourcesContent":["/**\n * @commet/next - Next.js integration for Commet\n *\n * @example\n * Webhooks\n * ```typescript\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Handle activation\n * },\n * });\n * ```\n *\n * @example\n * Customer Portal\n * ```typescript\n * import { CustomerPortal } from \"@commet/next\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { registerIntegration } from \"@commet/node\";\n\ndeclare const __PKG_VERSION__: string;\nregisterIntegration(\"@commet/next\", __PKG_VERSION__);\n\nexport type { CustomerPortalConfig } from \"./portal\";\nexport { CustomerPortal } from \"./portal\";\nexport type { PricingMarkdownConfig } from \"./pricing-markdown\";\nexport { PricingMarkdown } from \"./pricing-markdown\";\nexport type {\n WebhookData,\n WebhookEvent,\n WebhookPayload,\n WebhooksConfig,\n} from \"./types\";\nexport { Webhooks } from \"./webhooks\";\n","import { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface CustomerPortalConfig {\n apiKey: string;\n getCustomerId: (req: NextRequest) => Promise<string | null>;\n onError?: (error: Error) => void;\n}\n\n/**\n * Creates Next.js route handler for Customer Portal access\n *\n * @example\n * ```typescript\n * // app/api/commet/portal/route.ts\n * import { CustomerPortal } from \"@commet/next\";\n * import { auth } from \"@/lib/auth\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id ?? null;\n * },\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Use in component\n * export function BillingButton() {\n * return (\n * <Button asChild>\n * <Link href=\"/api/commet/portal\">Manage Billing</Link>\n * </Button>\n * );\n * }\n * ```\n */\nexport const CustomerPortal = ({\n apiKey,\n getCustomerId,\n onError,\n}: CustomerPortalConfig) => {\n const commet = new Commet({\n apiKey,\n });\n\n return async (req: NextRequest) => {\n try {\n // Get customer identifier from request\n const customerId = await getCustomerId(req);\n\n if (!customerId) {\n return NextResponse.json(\n { error: \"Customer not authenticated\" },\n { status: 401 },\n );\n }\n\n // Get portal URL from Commet\n const result = await commet.portal.getUrl({\n customerId,\n });\n\n if (!result.success || !result.data) {\n throw new Error(\n result.error?.message || \"Failed to create portal session\",\n );\n }\n\n // Redirect to customer portal\n return NextResponse.redirect(result.data.portalUrl);\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n // Call custom error handler if provided\n onError?.(errorObj);\n\n // Log error\n console.error(\"[Commet Portal]\", errorObj);\n\n // Return error response\n return NextResponse.json(\n { error: \"Failed to access customer portal\" },\n { status: 500 },\n );\n }\n };\n};\n","import type {\n BillingInterval,\n CreditPack,\n Plan,\n PlanFeature,\n} from \"@commet/node\";\nimport { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface PricingMarkdownConfig {\n apiKey: string;\n title: string;\n description?: string;\n includeCreditPacks?: boolean;\n billingIntervals?: BillingInterval[];\n cacheMaxAge?: number;\n onError?: (error: Error) => void;\n}\n\nconst INTERVAL_SHORT: Record<BillingInterval, string> = {\n weekly: \"/wk\",\n monthly: \"/mo\",\n quarterly: \"/qtr\",\n yearly: \"/yr\",\n one_time: \"\",\n};\n\nexport const PricingMarkdown = ({\n apiKey,\n title,\n description,\n includeCreditPacks = false,\n billingIntervals,\n cacheMaxAge = 3600,\n onError,\n}: PricingMarkdownConfig) => {\n const commet = new Commet({ apiKey });\n\n return async (_req: NextRequest) => {\n try {\n const plansResult = await commet.plans.list();\n\n if (!plansResult.success || !plansResult.data) {\n throw new Error(plansResult.error?.message ?? \"Failed to fetch plans\");\n }\n\n const publicPlans = plansResult.data.filter((plan) => plan.isPublic);\n\n const creditPacks = includeCreditPacks\n ? await commet.creditPacks\n .list()\n .then((result) =>\n result.success && result.data ? result.data : [],\n )\n : [];\n\n const markdown = generateMarkdown(\n title,\n description,\n publicPlans,\n creditPacks,\n billingIntervals,\n );\n\n return new NextResponse(markdown, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/markdown; charset=utf-8\",\n \"Cache-Control\": `public, s-maxage=${cacheMaxAge}, stale-while-revalidate=${cacheMaxAge * 2}`,\n },\n });\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n onError?.(errorObj);\n console.error(\"[Commet PricingMarkdown]\", errorObj);\n\n return NextResponse.json(\n { error: \"Failed to generate pricing markdown\" },\n { status: 500 },\n );\n }\n };\n};\n\nfunction generateMarkdown(\n title: string,\n description: string | undefined,\n plans: Plan[],\n creditPacks: CreditPack[],\n billingIntervals?: BillingInterval[],\n): string {\n const sections: string[] = [`# ${title}`];\n\n if (description) {\n sections.push(description);\n }\n\n if (plans.length > 0) {\n sections.push(renderPlansTable(plans, billingIntervals));\n\n const featuresSection = renderFeatures(plans);\n if (featuresSection) {\n sections.push(featuresSection);\n }\n }\n\n if (creditPacks.length > 0) {\n sections.push(renderCreditPacks(creditPacks));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nfunction renderPlansTable(\n plans: Plan[],\n filterIntervals?: BillingInterval[],\n): string {\n const usageFeatures = collectUsageFeatures(plans);\n\n const headerCols = [\"Plan\", \"Price\"];\n for (const { name, unitName } of usageFeatures) {\n headerCols.push(`${name}/${unitName ?? \"unit\"}`);\n headerCols.push(\"Overage\");\n }\n\n const rows = plans.map((plan) => {\n const priceCell = renderPriceCell(plan, filterIntervals);\n const cols = [plan.name, priceCell];\n\n const featuresByCode = new Map(\n plan.features.map((feature) => [feature.code, feature]),\n );\n\n for (const { code } of usageFeatures) {\n const feature = featuresByCode.get(code);\n\n if (!feature) {\n cols.push(\"—\", \"—\");\n continue;\n }\n\n if (feature.unlimited) {\n cols.push(\"Unlimited\", \"—\");\n continue;\n }\n\n const included = feature.includedAmount;\n cols.push(included ? formatNumber(included) : \"—\");\n\n const overagePrice =\n feature.overageEnabled && feature.overageUnitPrice !== undefined\n ? formatRatePrice(feature.overageUnitPrice)\n : \"—\";\n cols.push(overagePrice);\n }\n\n return cols;\n });\n\n const separator = headerCols.map(() => \"---\");\n const lines = [\n `| ${headerCols.join(\" | \")} |`,\n `| ${separator.join(\" | \")} |`,\n ...rows.map((cols) => `| ${cols.join(\" | \")} |`),\n ];\n\n const footnotes: string[] = [];\n\n const trialPlans = plans.filter((plan) =>\n plan.prices.some((price) => price.trialDays > 0),\n );\n for (const plan of trialPlans) {\n const trialPrice = plan.prices.find((price) => price.trialDays > 0)!;\n footnotes.push(\n `*${plan.name} plan includes a ${trialPrice.trialDays}-day free trial.*`,\n );\n }\n\n if (usageFeatures.length > 0) {\n footnotes.push(\n \"The overage rate applies only to usage beyond the included volume.\",\n );\n }\n\n return footnotes.length > 0\n ? `## Plans\\n\\n${lines.join(\"\\n\")}\\n\\n${footnotes.join(\"\\n\\n\")}`\n : `## Plans\\n\\n${lines.join(\"\\n\")}`;\n}\n\nfunction renderPriceCell(\n plan: Plan,\n filterIntervals?: BillingInterval[],\n): string {\n if (plan.isFree) return \"Free\";\n\n const prices = filterIntervals\n ? plan.prices.filter((price) =>\n filterIntervals.includes(price.billingInterval),\n )\n : plan.prices;\n\n if (prices.length === 0) return \"—\";\n\n return prices\n .map((price) => {\n if (price.price === 0) return \"Free\";\n return `${formatSettlementPrice(price.price)}${INTERVAL_SHORT[price.billingInterval]}`;\n })\n .join(\" / \");\n}\n\nfunction renderFeatures(plans: Plan[]): string | null {\n const allBooleanCodes = new Set<string>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (feature.type === \"boolean\") {\n allBooleanCodes.add(feature.code);\n }\n }\n }\n\n if (allBooleanCodes.size === 0) return null;\n\n const enabledOnAll = new Set<string>();\n for (const code of allBooleanCodes) {\n const enabledOnEveryPlan = plans.every((plan) => {\n const feature = plan.features.find((f) => f.code === code);\n return feature?.enabled;\n });\n if (enabledOnEveryPlan) {\n enabledOnAll.add(code);\n }\n }\n\n const perPlanLines: string[] = [];\n for (const plan of plans) {\n const uniqueFeatures = plan.features\n .filter(\n (feature) =>\n feature.type === \"boolean\" &&\n feature.enabled &&\n !enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n if (uniqueFeatures.length > 0) {\n perPlanLines.push(`- **${plan.name}**: ${uniqueFeatures.join(\", \")}`);\n }\n }\n\n const commonFeatures = plans[0]?.features\n .filter(\n (feature) => feature.type === \"boolean\" && enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n const parts: string[] = [];\n\n if (perPlanLines.length > 0) {\n parts.push(perPlanLines.join(\"\\n\"));\n }\n\n if (commonFeatures && commonFeatures.length > 0) {\n parts.push(`All plans include: ${commonFeatures.join(\", \")}.`);\n }\n\n return parts.length > 0 ? `## Features\\n\\n${parts.join(\"\\n\\n\")}` : null;\n}\n\nfunction renderCreditPacks(packs: CreditPack[]): string {\n const lines = [\n \"| Pack | Credits | Price |\",\n \"| --- | --- | --- |\",\n ...packs.map(\n (pack) =>\n `| ${pack.name} | ${formatNumber(pack.credits)} credits | ${formatSettlementPrice(pack.price)} |`,\n ),\n ];\n\n return `## Credit Packs\\n\\n${lines.join(\"\\n\")}`;\n}\n\ninterface UsageFeatureColumn {\n code: string;\n name: string;\n unitName: string | null;\n}\n\nfunction collectUsageFeatures(plans: Plan[]): UsageFeatureColumn[] {\n const seen = new Map<string, UsageFeatureColumn>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (\n (feature.type === \"usage\" || feature.type === \"seats\") &&\n !seen.has(feature.code) &&\n hasUsageData(feature)\n ) {\n seen.set(feature.code, {\n code: feature.code,\n name: feature.name,\n unitName: feature.unitName,\n });\n }\n }\n }\n return Array.from(seen.values());\n}\n\nfunction hasUsageData(feature: PlanFeature): boolean {\n return (\n feature.unlimited === true ||\n (feature.includedAmount !== undefined && feature.includedAmount > 0) ||\n (feature.overageEnabled === true && feature.overageUnitPrice !== undefined)\n );\n}\n\nfunction formatSettlementPrice(cents: number): string {\n if (cents === 0) return \"Free\";\n const dollars = cents / 100;\n const [whole, decimal] = dollars.toFixed(2).split(\".\");\n return `$${Number(whole).toLocaleString(\"en-US\")}.${decimal}`;\n}\n\nfunction formatRatePrice(rate: number): string {\n const dollars = rate / 10000;\n\n if (dollars === Math.floor(dollars)) {\n return `$${dollars.toFixed(2)}`;\n }\n\n const formatted = dollars.toFixed(4).replace(/0+$/, \"\");\n const decimalPlaces = formatted.split(\".\")[1]?.length ?? 0;\n if (decimalPlaces < 2) {\n return `$${dollars.toFixed(2)}`;\n }\n\n return `$${formatted}`;\n}\n\nfunction formatNumber(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n","import type { WebhookPayload } from \"@commet/node\";\nimport { Webhooks as CommetWebhooks } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\nimport type { WebhooksConfig } from \"./types\";\n\n/**\n * Create a Next.js webhook handler for Commet events\n *\n * Automatically verifies signatures, routes events to handlers, and returns proper responses.\n *\n * @param config - Webhook configuration with secret and event handlers\n * @returns Next.js route handler function\n *\n * @example\n * ```typescript\n * // app/api/webhooks/commet/route.ts\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Grant access to user\n * },\n * onSubscriptionCanceled: async (payload) => {\n * // Revoke access from user\n * },\n * });\n * ```\n */\nexport const Webhooks = (config: WebhooksConfig) => {\n const {\n webhookSecret,\n onSubscriptionActivated,\n onSubscriptionCanceled,\n onSubscriptionCreated,\n onSubscriptionUpdated,\n onSubscriptionPlanChanged,\n onPaymentReceived,\n onPaymentFailed,\n onInvoiceCreated,\n onPayload,\n onError,\n } = config;\n\n // Create webhook verifier instance\n const webhooks = new CommetWebhooks();\n\n return async (request: NextRequest) => {\n try {\n // 1. Read raw request body\n const rawBody = await request.text();\n\n // 2. Extract signature from headers\n const signature = request.headers.get(\"x-commet-signature\");\n\n // 3. Verify signature\n const isValid = webhooks.verify({\n payload: rawBody,\n signature,\n secret: webhookSecret,\n });\n\n if (!isValid) {\n console.error(\"[Commet Webhook] Invalid signature\");\n return NextResponse.json(\n { received: false, error: \"Invalid signature\" },\n { status: 403 },\n );\n }\n\n // 4. Parse payload\n let payload: WebhookPayload;\n try {\n payload = JSON.parse(rawBody) as WebhookPayload;\n } catch (parseError) {\n console.error(\"[Commet Webhook] Failed to parse payload:\", parseError);\n if (onError) {\n await onError(\n parseError instanceof Error\n ? parseError\n : new Error(\"Failed to parse webhook payload\"),\n rawBody,\n );\n }\n return NextResponse.json(\n { received: false, error: \"Invalid payload\" },\n { status: 400 },\n );\n }\n\n // 5. Collect promises for parallel execution\n const promises: Promise<void>[] = [];\n\n // Call catch-all handler if provided\n if (onPayload) {\n promises.push(onPayload(payload));\n }\n\n // 6. Route to specific event handler\n switch (payload.event) {\n case \"subscription.activated\":\n if (onSubscriptionActivated) {\n promises.push(onSubscriptionActivated(payload));\n }\n break;\n\n case \"subscription.canceled\":\n if (onSubscriptionCanceled) {\n promises.push(onSubscriptionCanceled(payload));\n }\n break;\n\n case \"subscription.created\":\n if (onSubscriptionCreated) {\n promises.push(onSubscriptionCreated(payload));\n }\n break;\n\n case \"subscription.updated\":\n if (onSubscriptionUpdated) {\n promises.push(onSubscriptionUpdated(payload));\n }\n break;\n\n case \"subscription.plan_changed\":\n if (onSubscriptionPlanChanged) {\n promises.push(onSubscriptionPlanChanged(payload));\n }\n break;\n\n case \"payment.received\":\n if (onPaymentReceived) {\n promises.push(onPaymentReceived(payload));\n }\n break;\n\n case \"payment.failed\":\n if (onPaymentFailed) {\n promises.push(onPaymentFailed(payload));\n }\n break;\n\n case \"invoice.created\":\n if (onInvoiceCreated) {\n promises.push(onInvoiceCreated(payload));\n }\n break;\n\n default:\n console.log(`[Commet Webhook] Unhandled event: ${payload.event}`);\n }\n\n // 7. Execute all handlers in parallel\n try {\n await Promise.all(promises);\n } catch (handlerError) {\n console.error(\n \"[Commet Webhook] Handler error:\",\n handlerError instanceof Error ? handlerError.message : handlerError,\n );\n if (onError) {\n await onError(\n handlerError instanceof Error\n ? handlerError\n : new Error(\"Handler execution failed\"),\n payload,\n );\n }\n // Still return 200 to prevent retries for handler errors\n return NextResponse.json(\n { received: true, warning: \"Handler failed\" },\n { status: 200 },\n );\n }\n\n // 8. Success response\n return NextResponse.json({ received: true }, { status: 200 });\n } catch (error) {\n console.error(\"[Commet Webhook] Unexpected error:\", error);\n if (onError) {\n try {\n await onError(\n error instanceof Error ? error : new Error(\"Unexpected error\"),\n undefined,\n );\n } catch (errorHandlerError) {\n console.error(\n \"[Commet Webhook] Error handler failed:\",\n errorHandlerError,\n );\n }\n }\n return NextResponse.json(\n { received: false, error: \"Internal error\" },\n { status: 500 },\n );\n }\n };\n};\n"],"mappings":"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","names":["import_node","import_node","import_server","import_node","import_server","CommetWebhooks"]}
|
package/dist/index.mjs
CHANGED
|
@@ -25,7 +25,9 @@ var CustomerPortal = ({
|
|
|
25
25
|
customerId
|
|
26
26
|
});
|
|
27
27
|
if (!result.success || !result.data) {
|
|
28
|
-
throw new Error(
|
|
28
|
+
throw new Error(
|
|
29
|
+
result.error?.message || "Failed to create portal session"
|
|
30
|
+
);
|
|
29
31
|
}
|
|
30
32
|
return NextResponse.redirect(result.data.portalUrl);
|
|
31
33
|
} catch (error) {
|
|
@@ -64,7 +66,7 @@ var PricingMarkdown = ({
|
|
|
64
66
|
try {
|
|
65
67
|
const plansResult = await commet.plans.list();
|
|
66
68
|
if (!plansResult.success || !plansResult.data) {
|
|
67
|
-
throw new Error(plansResult.message ?? "Failed to fetch plans");
|
|
69
|
+
throw new Error(plansResult.error?.message ?? "Failed to fetch plans");
|
|
68
70
|
}
|
|
69
71
|
const publicPlans = plansResult.data.filter((plan) => plan.isPublic);
|
|
70
72
|
const creditPacks = includeCreditPacks ? await commet.creditPacks.list().then(
|
|
@@ -287,6 +289,10 @@ var Webhooks = (config) => {
|
|
|
287
289
|
onSubscriptionCanceled,
|
|
288
290
|
onSubscriptionCreated,
|
|
289
291
|
onSubscriptionUpdated,
|
|
292
|
+
onSubscriptionPlanChanged,
|
|
293
|
+
onPaymentReceived,
|
|
294
|
+
onPaymentFailed,
|
|
295
|
+
onInvoiceCreated,
|
|
290
296
|
onPayload,
|
|
291
297
|
onError
|
|
292
298
|
} = config;
|
|
@@ -348,6 +354,26 @@ var Webhooks = (config) => {
|
|
|
348
354
|
promises.push(onSubscriptionUpdated(payload));
|
|
349
355
|
}
|
|
350
356
|
break;
|
|
357
|
+
case "subscription.plan_changed":
|
|
358
|
+
if (onSubscriptionPlanChanged) {
|
|
359
|
+
promises.push(onSubscriptionPlanChanged(payload));
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
case "payment.received":
|
|
363
|
+
if (onPaymentReceived) {
|
|
364
|
+
promises.push(onPaymentReceived(payload));
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
case "payment.failed":
|
|
368
|
+
if (onPaymentFailed) {
|
|
369
|
+
promises.push(onPaymentFailed(payload));
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
case "invoice.created":
|
|
373
|
+
if (onInvoiceCreated) {
|
|
374
|
+
promises.push(onInvoiceCreated(payload));
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
351
377
|
default:
|
|
352
378
|
console.log(`[Commet Webhook] Unhandled event: ${payload.event}`);
|
|
353
379
|
}
|
|
@@ -394,7 +420,7 @@ var Webhooks = (config) => {
|
|
|
394
420
|
};
|
|
395
421
|
|
|
396
422
|
// src/index.ts
|
|
397
|
-
registerIntegration("@commet/next", "0.
|
|
423
|
+
registerIntegration("@commet/next", "1.0.0");
|
|
398
424
|
export {
|
|
399
425
|
CustomerPortal,
|
|
400
426
|
PricingMarkdown,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/portal.ts","../src/pricing-markdown.ts","../src/webhooks.ts"],"sourcesContent":["/**\n * @commet/next - Next.js integration for Commet\n *\n * @example\n * Webhooks\n * ```typescript\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Handle activation\n * },\n * });\n * ```\n *\n * @example\n * Customer Portal\n * ```typescript\n * import { CustomerPortal } from \"@commet/next\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { registerIntegration } from \"@commet/node\";\n\ndeclare const __PKG_VERSION__: string;\nregisterIntegration(\"@commet/next\", __PKG_VERSION__);\n\nexport type { CustomerPortalConfig } from \"./portal\";\nexport { CustomerPortal } from \"./portal\";\nexport type { PricingMarkdownConfig } from \"./pricing-markdown\";\nexport { PricingMarkdown } from \"./pricing-markdown\";\nexport type {\n WebhookData,\n WebhookEvent,\n WebhookPayload,\n WebhooksConfig,\n} from \"./types\";\nexport { Webhooks } from \"./webhooks\";\n","import { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface CustomerPortalConfig {\n apiKey: string;\n getCustomerId: (req: NextRequest) => Promise<string | null>;\n onError?: (error: Error) => void;\n}\n\n/**\n * Creates Next.js route handler for Customer Portal access\n *\n * @example\n * ```typescript\n * // app/api/commet/portal/route.ts\n * import { CustomerPortal } from \"@commet/next\";\n * import { auth } from \"@/lib/auth\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id ?? null;\n * },\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Use in component\n * export function BillingButton() {\n * return (\n * <Button asChild>\n * <Link href=\"/api/commet/portal\">Manage Billing</Link>\n * </Button>\n * );\n * }\n * ```\n */\nexport const CustomerPortal = ({\n apiKey,\n getCustomerId,\n onError,\n}: CustomerPortalConfig) => {\n const commet = new Commet({\n apiKey,\n });\n\n return async (req: NextRequest) => {\n try {\n // Get customer identifier from request\n const customerId = await getCustomerId(req);\n\n if (!customerId) {\n return NextResponse.json(\n { error: \"Customer not authenticated\" },\n { status: 401 },\n );\n }\n\n // Get portal URL from Commet\n const result = await commet.portal.getUrl({\n customerId,\n });\n\n if (!result.success || !result.data) {\n throw new Error(result.message || \"Failed to create portal session\");\n }\n\n // Redirect to customer portal\n return NextResponse.redirect(result.data.portalUrl);\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n // Call custom error handler if provided\n onError?.(errorObj);\n\n // Log error\n console.error(\"[Commet Portal]\", errorObj);\n\n // Return error response\n return NextResponse.json(\n { error: \"Failed to access customer portal\" },\n { status: 500 },\n );\n }\n };\n};\n","import type {\n BillingInterval,\n CreditPack,\n Plan,\n PlanFeature,\n} from \"@commet/node\";\nimport { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface PricingMarkdownConfig {\n apiKey: string;\n title: string;\n description?: string;\n includeCreditPacks?: boolean;\n billingIntervals?: BillingInterval[];\n cacheMaxAge?: number;\n onError?: (error: Error) => void;\n}\n\nconst INTERVAL_SHORT: Record<BillingInterval, string> = {\n weekly: \"/wk\",\n monthly: \"/mo\",\n quarterly: \"/qtr\",\n yearly: \"/yr\",\n one_time: \"\",\n};\n\nexport const PricingMarkdown = ({\n apiKey,\n title,\n description,\n includeCreditPacks = false,\n billingIntervals,\n cacheMaxAge = 3600,\n onError,\n}: PricingMarkdownConfig) => {\n const commet = new Commet({ apiKey });\n\n return async (_req: NextRequest) => {\n try {\n const plansResult = await commet.plans.list();\n\n if (!plansResult.success || !plansResult.data) {\n throw new Error(plansResult.message ?? \"Failed to fetch plans\");\n }\n\n const publicPlans = plansResult.data.filter((plan) => plan.isPublic);\n\n const creditPacks = includeCreditPacks\n ? await commet.creditPacks\n .list()\n .then((result) =>\n result.success && result.data ? result.data : [],\n )\n : [];\n\n const markdown = generateMarkdown(\n title,\n description,\n publicPlans,\n creditPacks,\n billingIntervals,\n );\n\n return new NextResponse(markdown, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/markdown; charset=utf-8\",\n \"Cache-Control\": `public, s-maxage=${cacheMaxAge}, stale-while-revalidate=${cacheMaxAge * 2}`,\n },\n });\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n onError?.(errorObj);\n console.error(\"[Commet PricingMarkdown]\", errorObj);\n\n return NextResponse.json(\n { error: \"Failed to generate pricing markdown\" },\n { status: 500 },\n );\n }\n };\n};\n\nfunction generateMarkdown(\n title: string,\n description: string | undefined,\n plans: Plan[],\n creditPacks: CreditPack[],\n billingIntervals?: BillingInterval[],\n): string {\n const sections: string[] = [`# ${title}`];\n\n if (description) {\n sections.push(description);\n }\n\n if (plans.length > 0) {\n sections.push(renderPlansTable(plans, billingIntervals));\n\n const featuresSection = renderFeatures(plans);\n if (featuresSection) {\n sections.push(featuresSection);\n }\n }\n\n if (creditPacks.length > 0) {\n sections.push(renderCreditPacks(creditPacks));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nfunction renderPlansTable(\n plans: Plan[],\n filterIntervals?: BillingInterval[],\n): string {\n const usageFeatures = collectUsageFeatures(plans);\n\n const headerCols = [\"Plan\", \"Price\"];\n for (const { name, unitName } of usageFeatures) {\n headerCols.push(`${name}/${unitName ?? \"unit\"}`);\n headerCols.push(\"Overage\");\n }\n\n const rows = plans.map((plan) => {\n const priceCell = renderPriceCell(plan, filterIntervals);\n const cols = [plan.name, priceCell];\n\n const featuresByCode = new Map(\n plan.features.map((feature) => [feature.code, feature]),\n );\n\n for (const { code } of usageFeatures) {\n const feature = featuresByCode.get(code);\n\n if (!feature) {\n cols.push(\"—\", \"—\");\n continue;\n }\n\n if (feature.unlimited) {\n cols.push(\"Unlimited\", \"—\");\n continue;\n }\n\n const included = feature.includedAmount;\n cols.push(included ? formatNumber(included) : \"—\");\n\n const overagePrice =\n feature.overageEnabled && feature.overageUnitPrice !== undefined\n ? formatRatePrice(feature.overageUnitPrice)\n : \"—\";\n cols.push(overagePrice);\n }\n\n return cols;\n });\n\n const separator = headerCols.map(() => \"---\");\n const lines = [\n `| ${headerCols.join(\" | \")} |`,\n `| ${separator.join(\" | \")} |`,\n ...rows.map((cols) => `| ${cols.join(\" | \")} |`),\n ];\n\n const footnotes: string[] = [];\n\n const trialPlans = plans.filter((plan) =>\n plan.prices.some((price) => price.trialDays > 0),\n );\n for (const plan of trialPlans) {\n const trialPrice = plan.prices.find((price) => price.trialDays > 0)!;\n footnotes.push(\n `*${plan.name} plan includes a ${trialPrice.trialDays}-day free trial.*`,\n );\n }\n\n if (usageFeatures.length > 0) {\n footnotes.push(\n \"The overage rate applies only to usage beyond the included volume.\",\n );\n }\n\n return footnotes.length > 0\n ? `## Plans\\n\\n${lines.join(\"\\n\")}\\n\\n${footnotes.join(\"\\n\\n\")}`\n : `## Plans\\n\\n${lines.join(\"\\n\")}`;\n}\n\nfunction renderPriceCell(\n plan: Plan,\n filterIntervals?: BillingInterval[],\n): string {\n if (plan.isFree) return \"Free\";\n\n const prices = filterIntervals\n ? plan.prices.filter((price) =>\n filterIntervals.includes(price.billingInterval),\n )\n : plan.prices;\n\n if (prices.length === 0) return \"—\";\n\n return prices\n .map((price) => {\n if (price.price === 0) return \"Free\";\n return `${formatSettlementPrice(price.price)}${INTERVAL_SHORT[price.billingInterval]}`;\n })\n .join(\" / \");\n}\n\nfunction renderFeatures(plans: Plan[]): string | null {\n const allBooleanCodes = new Set<string>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (feature.type === \"boolean\") {\n allBooleanCodes.add(feature.code);\n }\n }\n }\n\n if (allBooleanCodes.size === 0) return null;\n\n const enabledOnAll = new Set<string>();\n for (const code of allBooleanCodes) {\n const enabledOnEveryPlan = plans.every((plan) => {\n const feature = plan.features.find((f) => f.code === code);\n return feature?.enabled;\n });\n if (enabledOnEveryPlan) {\n enabledOnAll.add(code);\n }\n }\n\n const perPlanLines: string[] = [];\n for (const plan of plans) {\n const uniqueFeatures = plan.features\n .filter(\n (feature) =>\n feature.type === \"boolean\" &&\n feature.enabled &&\n !enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n if (uniqueFeatures.length > 0) {\n perPlanLines.push(`- **${plan.name}**: ${uniqueFeatures.join(\", \")}`);\n }\n }\n\n const commonFeatures = plans[0]?.features\n .filter(\n (feature) => feature.type === \"boolean\" && enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n const parts: string[] = [];\n\n if (perPlanLines.length > 0) {\n parts.push(perPlanLines.join(\"\\n\"));\n }\n\n if (commonFeatures && commonFeatures.length > 0) {\n parts.push(`All plans include: ${commonFeatures.join(\", \")}.`);\n }\n\n return parts.length > 0 ? `## Features\\n\\n${parts.join(\"\\n\\n\")}` : null;\n}\n\nfunction renderCreditPacks(packs: CreditPack[]): string {\n const lines = [\n \"| Pack | Credits | Price |\",\n \"| --- | --- | --- |\",\n ...packs.map(\n (pack) =>\n `| ${pack.name} | ${formatNumber(pack.credits)} credits | ${formatSettlementPrice(pack.price)} |`,\n ),\n ];\n\n return `## Credit Packs\\n\\n${lines.join(\"\\n\")}`;\n}\n\ninterface UsageFeatureColumn {\n code: string;\n name: string;\n unitName: string | null;\n}\n\nfunction collectUsageFeatures(plans: Plan[]): UsageFeatureColumn[] {\n const seen = new Map<string, UsageFeatureColumn>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (\n (feature.type === \"usage\" || feature.type === \"seats\") &&\n !seen.has(feature.code) &&\n hasUsageData(feature)\n ) {\n seen.set(feature.code, {\n code: feature.code,\n name: feature.name,\n unitName: feature.unitName,\n });\n }\n }\n }\n return Array.from(seen.values());\n}\n\nfunction hasUsageData(feature: PlanFeature): boolean {\n return (\n feature.unlimited === true ||\n (feature.includedAmount !== undefined && feature.includedAmount > 0) ||\n (feature.overageEnabled === true && feature.overageUnitPrice !== undefined)\n );\n}\n\nfunction formatSettlementPrice(cents: number): string {\n if (cents === 0) return \"Free\";\n const dollars = cents / 100;\n const [whole, decimal] = dollars.toFixed(2).split(\".\");\n return `$${Number(whole).toLocaleString(\"en-US\")}.${decimal}`;\n}\n\nfunction formatRatePrice(rate: number): string {\n const dollars = rate / 10000;\n\n if (dollars === Math.floor(dollars)) {\n return `$${dollars.toFixed(2)}`;\n }\n\n const formatted = dollars.toFixed(4).replace(/0+$/, \"\");\n const decimalPlaces = formatted.split(\".\")[1]?.length ?? 0;\n if (decimalPlaces < 2) {\n return `$${dollars.toFixed(2)}`;\n }\n\n return `$${formatted}`;\n}\n\nfunction formatNumber(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n","import type { WebhookPayload } from \"@commet/node\";\nimport { Webhooks as CommetWebhooks } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\nimport type { WebhooksConfig } from \"./types\";\n\n/**\n * Create a Next.js webhook handler for Commet events\n *\n * Automatically verifies signatures, routes events to handlers, and returns proper responses.\n *\n * @param config - Webhook configuration with secret and event handlers\n * @returns Next.js route handler function\n *\n * @example\n * ```typescript\n * // app/api/webhooks/commet/route.ts\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Grant access to user\n * },\n * onSubscriptionCanceled: async (payload) => {\n * // Revoke access from user\n * },\n * });\n * ```\n */\nexport const Webhooks = (config: WebhooksConfig) => {\n const {\n webhookSecret,\n onSubscriptionActivated,\n onSubscriptionCanceled,\n onSubscriptionCreated,\n onSubscriptionUpdated,\n onPayload,\n onError,\n } = config;\n\n // Create webhook verifier instance\n const webhooks = new CommetWebhooks();\n\n return async (request: NextRequest) => {\n try {\n // 1. Read raw request body\n const rawBody = await request.text();\n\n // 2. Extract signature from headers\n const signature = request.headers.get(\"x-commet-signature\");\n\n // 3. Verify signature\n const isValid = webhooks.verify({\n payload: rawBody,\n signature,\n secret: webhookSecret,\n });\n\n if (!isValid) {\n console.error(\"[Commet Webhook] Invalid signature\");\n return NextResponse.json(\n { received: false, error: \"Invalid signature\" },\n { status: 403 },\n );\n }\n\n // 4. Parse payload\n let payload: WebhookPayload;\n try {\n payload = JSON.parse(rawBody) as WebhookPayload;\n } catch (parseError) {\n console.error(\"[Commet Webhook] Failed to parse payload:\", parseError);\n if (onError) {\n await onError(\n parseError instanceof Error\n ? parseError\n : new Error(\"Failed to parse webhook payload\"),\n rawBody,\n );\n }\n return NextResponse.json(\n { received: false, error: \"Invalid payload\" },\n { status: 400 },\n );\n }\n\n // 5. Collect promises for parallel execution\n const promises: Promise<void>[] = [];\n\n // Call catch-all handler if provided\n if (onPayload) {\n promises.push(onPayload(payload));\n }\n\n // 6. Route to specific event handler\n switch (payload.event) {\n case \"subscription.activated\":\n if (onSubscriptionActivated) {\n promises.push(onSubscriptionActivated(payload));\n }\n break;\n\n case \"subscription.canceled\":\n if (onSubscriptionCanceled) {\n promises.push(onSubscriptionCanceled(payload));\n }\n break;\n\n case \"subscription.created\":\n if (onSubscriptionCreated) {\n promises.push(onSubscriptionCreated(payload));\n }\n break;\n\n case \"subscription.updated\":\n if (onSubscriptionUpdated) {\n promises.push(onSubscriptionUpdated(payload));\n }\n break;\n\n default:\n console.log(`[Commet Webhook] Unhandled event: ${payload.event}`);\n }\n\n // 7. Execute all handlers in parallel\n try {\n await Promise.all(promises);\n } catch (handlerError) {\n console.error(\n \"[Commet Webhook] Handler error:\",\n handlerError instanceof Error ? handlerError.message : handlerError,\n );\n if (onError) {\n await onError(\n handlerError instanceof Error\n ? handlerError\n : new Error(\"Handler execution failed\"),\n payload,\n );\n }\n // Still return 200 to prevent retries for handler errors\n return NextResponse.json(\n { received: true, warning: \"Handler failed\" },\n { status: 200 },\n );\n }\n\n // 8. Success response\n return NextResponse.json({ received: true }, { status: 200 });\n } catch (error) {\n console.error(\"[Commet Webhook] Unexpected error:\", error);\n if (onError) {\n try {\n await onError(\n error instanceof Error ? error : new Error(\"Unexpected error\"),\n undefined,\n );\n } catch (errorHandlerError) {\n console.error(\n \"[Commet Webhook] Error handler failed:\",\n errorHandlerError,\n );\n }\n }\n return NextResponse.json(\n { received: false, error: \"Internal error\" },\n { status: 500 },\n );\n }\n };\n};\n"],"mappings":"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","names":["Commet","NextResponse","NextResponse"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/portal.ts","../src/pricing-markdown.ts","../src/webhooks.ts"],"sourcesContent":["/**\n * @commet/next - Next.js integration for Commet\n *\n * @example\n * Webhooks\n * ```typescript\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Handle activation\n * },\n * });\n * ```\n *\n * @example\n * Customer Portal\n * ```typescript\n * import { CustomerPortal } from \"@commet/next\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { registerIntegration } from \"@commet/node\";\n\ndeclare const __PKG_VERSION__: string;\nregisterIntegration(\"@commet/next\", __PKG_VERSION__);\n\nexport type { CustomerPortalConfig } from \"./portal\";\nexport { CustomerPortal } from \"./portal\";\nexport type { PricingMarkdownConfig } from \"./pricing-markdown\";\nexport { PricingMarkdown } from \"./pricing-markdown\";\nexport type {\n WebhookData,\n WebhookEvent,\n WebhookPayload,\n WebhooksConfig,\n} from \"./types\";\nexport { Webhooks } from \"./webhooks\";\n","import { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface CustomerPortalConfig {\n apiKey: string;\n getCustomerId: (req: NextRequest) => Promise<string | null>;\n onError?: (error: Error) => void;\n}\n\n/**\n * Creates Next.js route handler for Customer Portal access\n *\n * @example\n * ```typescript\n * // app/api/commet/portal/route.ts\n * import { CustomerPortal } from \"@commet/next\";\n * import { auth } from \"@/lib/auth\";\n *\n * export const GET = CustomerPortal({\n * apiKey: process.env.COMMET_API_KEY!,\n * getCustomerId: async (req) => {\n * const session = await auth.api.getSession({ headers: req.headers });\n * return session?.user.id ?? null;\n * },\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Use in component\n * export function BillingButton() {\n * return (\n * <Button asChild>\n * <Link href=\"/api/commet/portal\">Manage Billing</Link>\n * </Button>\n * );\n * }\n * ```\n */\nexport const CustomerPortal = ({\n apiKey,\n getCustomerId,\n onError,\n}: CustomerPortalConfig) => {\n const commet = new Commet({\n apiKey,\n });\n\n return async (req: NextRequest) => {\n try {\n // Get customer identifier from request\n const customerId = await getCustomerId(req);\n\n if (!customerId) {\n return NextResponse.json(\n { error: \"Customer not authenticated\" },\n { status: 401 },\n );\n }\n\n // Get portal URL from Commet\n const result = await commet.portal.getUrl({\n customerId,\n });\n\n if (!result.success || !result.data) {\n throw new Error(\n result.error?.message || \"Failed to create portal session\",\n );\n }\n\n // Redirect to customer portal\n return NextResponse.redirect(result.data.portalUrl);\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n // Call custom error handler if provided\n onError?.(errorObj);\n\n // Log error\n console.error(\"[Commet Portal]\", errorObj);\n\n // Return error response\n return NextResponse.json(\n { error: \"Failed to access customer portal\" },\n { status: 500 },\n );\n }\n };\n};\n","import type {\n BillingInterval,\n CreditPack,\n Plan,\n PlanFeature,\n} from \"@commet/node\";\nimport { Commet } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\n\nexport interface PricingMarkdownConfig {\n apiKey: string;\n title: string;\n description?: string;\n includeCreditPacks?: boolean;\n billingIntervals?: BillingInterval[];\n cacheMaxAge?: number;\n onError?: (error: Error) => void;\n}\n\nconst INTERVAL_SHORT: Record<BillingInterval, string> = {\n weekly: \"/wk\",\n monthly: \"/mo\",\n quarterly: \"/qtr\",\n yearly: \"/yr\",\n one_time: \"\",\n};\n\nexport const PricingMarkdown = ({\n apiKey,\n title,\n description,\n includeCreditPacks = false,\n billingIntervals,\n cacheMaxAge = 3600,\n onError,\n}: PricingMarkdownConfig) => {\n const commet = new Commet({ apiKey });\n\n return async (_req: NextRequest) => {\n try {\n const plansResult = await commet.plans.list();\n\n if (!plansResult.success || !plansResult.data) {\n throw new Error(plansResult.error?.message ?? \"Failed to fetch plans\");\n }\n\n const publicPlans = plansResult.data.filter((plan) => plan.isPublic);\n\n const creditPacks = includeCreditPacks\n ? await commet.creditPacks\n .list()\n .then((result) =>\n result.success && result.data ? result.data : [],\n )\n : [];\n\n const markdown = generateMarkdown(\n title,\n description,\n publicPlans,\n creditPacks,\n billingIntervals,\n );\n\n return new NextResponse(markdown, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/markdown; charset=utf-8\",\n \"Cache-Control\": `public, s-maxage=${cacheMaxAge}, stale-while-revalidate=${cacheMaxAge * 2}`,\n },\n });\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n onError?.(errorObj);\n console.error(\"[Commet PricingMarkdown]\", errorObj);\n\n return NextResponse.json(\n { error: \"Failed to generate pricing markdown\" },\n { status: 500 },\n );\n }\n };\n};\n\nfunction generateMarkdown(\n title: string,\n description: string | undefined,\n plans: Plan[],\n creditPacks: CreditPack[],\n billingIntervals?: BillingInterval[],\n): string {\n const sections: string[] = [`# ${title}`];\n\n if (description) {\n sections.push(description);\n }\n\n if (plans.length > 0) {\n sections.push(renderPlansTable(plans, billingIntervals));\n\n const featuresSection = renderFeatures(plans);\n if (featuresSection) {\n sections.push(featuresSection);\n }\n }\n\n if (creditPacks.length > 0) {\n sections.push(renderCreditPacks(creditPacks));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nfunction renderPlansTable(\n plans: Plan[],\n filterIntervals?: BillingInterval[],\n): string {\n const usageFeatures = collectUsageFeatures(plans);\n\n const headerCols = [\"Plan\", \"Price\"];\n for (const { name, unitName } of usageFeatures) {\n headerCols.push(`${name}/${unitName ?? \"unit\"}`);\n headerCols.push(\"Overage\");\n }\n\n const rows = plans.map((plan) => {\n const priceCell = renderPriceCell(plan, filterIntervals);\n const cols = [plan.name, priceCell];\n\n const featuresByCode = new Map(\n plan.features.map((feature) => [feature.code, feature]),\n );\n\n for (const { code } of usageFeatures) {\n const feature = featuresByCode.get(code);\n\n if (!feature) {\n cols.push(\"—\", \"—\");\n continue;\n }\n\n if (feature.unlimited) {\n cols.push(\"Unlimited\", \"—\");\n continue;\n }\n\n const included = feature.includedAmount;\n cols.push(included ? formatNumber(included) : \"—\");\n\n const overagePrice =\n feature.overageEnabled && feature.overageUnitPrice !== undefined\n ? formatRatePrice(feature.overageUnitPrice)\n : \"—\";\n cols.push(overagePrice);\n }\n\n return cols;\n });\n\n const separator = headerCols.map(() => \"---\");\n const lines = [\n `| ${headerCols.join(\" | \")} |`,\n `| ${separator.join(\" | \")} |`,\n ...rows.map((cols) => `| ${cols.join(\" | \")} |`),\n ];\n\n const footnotes: string[] = [];\n\n const trialPlans = plans.filter((plan) =>\n plan.prices.some((price) => price.trialDays > 0),\n );\n for (const plan of trialPlans) {\n const trialPrice = plan.prices.find((price) => price.trialDays > 0)!;\n footnotes.push(\n `*${plan.name} plan includes a ${trialPrice.trialDays}-day free trial.*`,\n );\n }\n\n if (usageFeatures.length > 0) {\n footnotes.push(\n \"The overage rate applies only to usage beyond the included volume.\",\n );\n }\n\n return footnotes.length > 0\n ? `## Plans\\n\\n${lines.join(\"\\n\")}\\n\\n${footnotes.join(\"\\n\\n\")}`\n : `## Plans\\n\\n${lines.join(\"\\n\")}`;\n}\n\nfunction renderPriceCell(\n plan: Plan,\n filterIntervals?: BillingInterval[],\n): string {\n if (plan.isFree) return \"Free\";\n\n const prices = filterIntervals\n ? plan.prices.filter((price) =>\n filterIntervals.includes(price.billingInterval),\n )\n : plan.prices;\n\n if (prices.length === 0) return \"—\";\n\n return prices\n .map((price) => {\n if (price.price === 0) return \"Free\";\n return `${formatSettlementPrice(price.price)}${INTERVAL_SHORT[price.billingInterval]}`;\n })\n .join(\" / \");\n}\n\nfunction renderFeatures(plans: Plan[]): string | null {\n const allBooleanCodes = new Set<string>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (feature.type === \"boolean\") {\n allBooleanCodes.add(feature.code);\n }\n }\n }\n\n if (allBooleanCodes.size === 0) return null;\n\n const enabledOnAll = new Set<string>();\n for (const code of allBooleanCodes) {\n const enabledOnEveryPlan = plans.every((plan) => {\n const feature = plan.features.find((f) => f.code === code);\n return feature?.enabled;\n });\n if (enabledOnEveryPlan) {\n enabledOnAll.add(code);\n }\n }\n\n const perPlanLines: string[] = [];\n for (const plan of plans) {\n const uniqueFeatures = plan.features\n .filter(\n (feature) =>\n feature.type === \"boolean\" &&\n feature.enabled &&\n !enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n if (uniqueFeatures.length > 0) {\n perPlanLines.push(`- **${plan.name}**: ${uniqueFeatures.join(\", \")}`);\n }\n }\n\n const commonFeatures = plans[0]?.features\n .filter(\n (feature) => feature.type === \"boolean\" && enabledOnAll.has(feature.code),\n )\n .map((feature) => feature.name);\n\n const parts: string[] = [];\n\n if (perPlanLines.length > 0) {\n parts.push(perPlanLines.join(\"\\n\"));\n }\n\n if (commonFeatures && commonFeatures.length > 0) {\n parts.push(`All plans include: ${commonFeatures.join(\", \")}.`);\n }\n\n return parts.length > 0 ? `## Features\\n\\n${parts.join(\"\\n\\n\")}` : null;\n}\n\nfunction renderCreditPacks(packs: CreditPack[]): string {\n const lines = [\n \"| Pack | Credits | Price |\",\n \"| --- | --- | --- |\",\n ...packs.map(\n (pack) =>\n `| ${pack.name} | ${formatNumber(pack.credits)} credits | ${formatSettlementPrice(pack.price)} |`,\n ),\n ];\n\n return `## Credit Packs\\n\\n${lines.join(\"\\n\")}`;\n}\n\ninterface UsageFeatureColumn {\n code: string;\n name: string;\n unitName: string | null;\n}\n\nfunction collectUsageFeatures(plans: Plan[]): UsageFeatureColumn[] {\n const seen = new Map<string, UsageFeatureColumn>();\n for (const plan of plans) {\n for (const feature of plan.features) {\n if (\n (feature.type === \"usage\" || feature.type === \"seats\") &&\n !seen.has(feature.code) &&\n hasUsageData(feature)\n ) {\n seen.set(feature.code, {\n code: feature.code,\n name: feature.name,\n unitName: feature.unitName,\n });\n }\n }\n }\n return Array.from(seen.values());\n}\n\nfunction hasUsageData(feature: PlanFeature): boolean {\n return (\n feature.unlimited === true ||\n (feature.includedAmount !== undefined && feature.includedAmount > 0) ||\n (feature.overageEnabled === true && feature.overageUnitPrice !== undefined)\n );\n}\n\nfunction formatSettlementPrice(cents: number): string {\n if (cents === 0) return \"Free\";\n const dollars = cents / 100;\n const [whole, decimal] = dollars.toFixed(2).split(\".\");\n return `$${Number(whole).toLocaleString(\"en-US\")}.${decimal}`;\n}\n\nfunction formatRatePrice(rate: number): string {\n const dollars = rate / 10000;\n\n if (dollars === Math.floor(dollars)) {\n return `$${dollars.toFixed(2)}`;\n }\n\n const formatted = dollars.toFixed(4).replace(/0+$/, \"\");\n const decimalPlaces = formatted.split(\".\")[1]?.length ?? 0;\n if (decimalPlaces < 2) {\n return `$${dollars.toFixed(2)}`;\n }\n\n return `$${formatted}`;\n}\n\nfunction formatNumber(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n","import type { WebhookPayload } from \"@commet/node\";\nimport { Webhooks as CommetWebhooks } from \"@commet/node\";\nimport { type NextRequest, NextResponse } from \"next/server\";\nimport type { WebhooksConfig } from \"./types\";\n\n/**\n * Create a Next.js webhook handler for Commet events\n *\n * Automatically verifies signatures, routes events to handlers, and returns proper responses.\n *\n * @param config - Webhook configuration with secret and event handlers\n * @returns Next.js route handler function\n *\n * @example\n * ```typescript\n * // app/api/webhooks/commet/route.ts\n * import { Webhooks } from \"@commet/next\";\n *\n * export const POST = Webhooks({\n * webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,\n * onSubscriptionActivated: async (payload) => {\n * // Grant access to user\n * },\n * onSubscriptionCanceled: async (payload) => {\n * // Revoke access from user\n * },\n * });\n * ```\n */\nexport const Webhooks = (config: WebhooksConfig) => {\n const {\n webhookSecret,\n onSubscriptionActivated,\n onSubscriptionCanceled,\n onSubscriptionCreated,\n onSubscriptionUpdated,\n onSubscriptionPlanChanged,\n onPaymentReceived,\n onPaymentFailed,\n onInvoiceCreated,\n onPayload,\n onError,\n } = config;\n\n // Create webhook verifier instance\n const webhooks = new CommetWebhooks();\n\n return async (request: NextRequest) => {\n try {\n // 1. Read raw request body\n const rawBody = await request.text();\n\n // 2. Extract signature from headers\n const signature = request.headers.get(\"x-commet-signature\");\n\n // 3. Verify signature\n const isValid = webhooks.verify({\n payload: rawBody,\n signature,\n secret: webhookSecret,\n });\n\n if (!isValid) {\n console.error(\"[Commet Webhook] Invalid signature\");\n return NextResponse.json(\n { received: false, error: \"Invalid signature\" },\n { status: 403 },\n );\n }\n\n // 4. Parse payload\n let payload: WebhookPayload;\n try {\n payload = JSON.parse(rawBody) as WebhookPayload;\n } catch (parseError) {\n console.error(\"[Commet Webhook] Failed to parse payload:\", parseError);\n if (onError) {\n await onError(\n parseError instanceof Error\n ? parseError\n : new Error(\"Failed to parse webhook payload\"),\n rawBody,\n );\n }\n return NextResponse.json(\n { received: false, error: \"Invalid payload\" },\n { status: 400 },\n );\n }\n\n // 5. Collect promises for parallel execution\n const promises: Promise<void>[] = [];\n\n // Call catch-all handler if provided\n if (onPayload) {\n promises.push(onPayload(payload));\n }\n\n // 6. Route to specific event handler\n switch (payload.event) {\n case \"subscription.activated\":\n if (onSubscriptionActivated) {\n promises.push(onSubscriptionActivated(payload));\n }\n break;\n\n case \"subscription.canceled\":\n if (onSubscriptionCanceled) {\n promises.push(onSubscriptionCanceled(payload));\n }\n break;\n\n case \"subscription.created\":\n if (onSubscriptionCreated) {\n promises.push(onSubscriptionCreated(payload));\n }\n break;\n\n case \"subscription.updated\":\n if (onSubscriptionUpdated) {\n promises.push(onSubscriptionUpdated(payload));\n }\n break;\n\n case \"subscription.plan_changed\":\n if (onSubscriptionPlanChanged) {\n promises.push(onSubscriptionPlanChanged(payload));\n }\n break;\n\n case \"payment.received\":\n if (onPaymentReceived) {\n promises.push(onPaymentReceived(payload));\n }\n break;\n\n case \"payment.failed\":\n if (onPaymentFailed) {\n promises.push(onPaymentFailed(payload));\n }\n break;\n\n case \"invoice.created\":\n if (onInvoiceCreated) {\n promises.push(onInvoiceCreated(payload));\n }\n break;\n\n default:\n console.log(`[Commet Webhook] Unhandled event: ${payload.event}`);\n }\n\n // 7. Execute all handlers in parallel\n try {\n await Promise.all(promises);\n } catch (handlerError) {\n console.error(\n \"[Commet Webhook] Handler error:\",\n handlerError instanceof Error ? handlerError.message : handlerError,\n );\n if (onError) {\n await onError(\n handlerError instanceof Error\n ? handlerError\n : new Error(\"Handler execution failed\"),\n payload,\n );\n }\n // Still return 200 to prevent retries for handler errors\n return NextResponse.json(\n { received: true, warning: \"Handler failed\" },\n { status: 200 },\n );\n }\n\n // 8. Success response\n return NextResponse.json({ received: true }, { status: 200 });\n } catch (error) {\n console.error(\"[Commet Webhook] Unexpected error:\", error);\n if (onError) {\n try {\n await onError(\n error instanceof Error ? error : new Error(\"Unexpected error\"),\n undefined,\n );\n } catch (errorHandlerError) {\n console.error(\n \"[Commet Webhook] Error handler failed:\",\n errorHandlerError,\n );\n }\n }\n return NextResponse.json(\n { received: false, error: \"Internal error\" },\n { status: 500 },\n );\n }\n };\n};\n"],"mappings":";AAiCA,SAAS,2BAA2B;;;ACjCpC,SAAS,cAAc;AACvB,SAA2B,oBAAoB;AAsCxC,IAAM,iBAAiB,CAAC;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,MAA4B;AAC1B,QAAM,SAAS,IAAI,OAAO;AAAA,IACxB;AAAA,EACF,CAAC;AAED,SAAO,OAAO,QAAqB;AACjC,QAAI;AAEF,YAAM,aAAa,MAAM,cAAc,GAAG;AAE1C,UAAI,CAAC,YAAY;AACf,eAAO,aAAa;AAAA,UAClB,EAAE,OAAO,6BAA6B;AAAA,UACtC,EAAE,QAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AAGA,YAAM,SAAS,MAAM,OAAO,OAAO,OAAO;AAAA,QACxC;AAAA,MACF,CAAC;AAED,UAAI,CAAC,OAAO,WAAW,CAAC,OAAO,MAAM;AACnC,cAAM,IAAI;AAAA,UACR,OAAO,OAAO,WAAW;AAAA,QAC3B;AAAA,MACF;AAGA,aAAO,aAAa,SAAS,OAAO,KAAK,SAAS;AAAA,IACpD,SAAS,OAAO;AACd,YAAM,WACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAG1D,gBAAU,QAAQ;AAGlB,cAAQ,MAAM,mBAAmB,QAAQ;AAGzC,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,mCAAmC;AAAA,QAC5C,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;;;ACpFA,SAAS,UAAAA,eAAc;AACvB,SAA2B,gBAAAC,qBAAoB;AAY/C,IAAM,iBAAkD;AAAA,EACtD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU;AACZ;AAEO,IAAM,kBAAkB,CAAC;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA,cAAc;AAAA,EACd;AACF,MAA6B;AAC3B,QAAM,SAAS,IAAID,QAAO,EAAE,OAAO,CAAC;AAEpC,SAAO,OAAO,SAAsB;AAClC,QAAI;AACF,YAAM,cAAc,MAAM,OAAO,MAAM,KAAK;AAE5C,UAAI,CAAC,YAAY,WAAW,CAAC,YAAY,MAAM;AAC7C,cAAM,IAAI,MAAM,YAAY,OAAO,WAAW,uBAAuB;AAAA,MACvE;AAEA,YAAM,cAAc,YAAY,KAAK,OAAO,CAAC,SAAS,KAAK,QAAQ;AAEnE,YAAM,cAAc,qBAChB,MAAM,OAAO,YACV,KAAK,EACL;AAAA,QAAK,CAAC,WACL,OAAO,WAAW,OAAO,OAAO,OAAO,OAAO,CAAC;AAAA,MACjD,IACF,CAAC;AAEL,YAAM,WAAW;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,aAAO,IAAIC,cAAa,UAAU;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB,oBAAoB,WAAW,4BAA4B,cAAc,CAAC;AAAA,QAC7F;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,WACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAE1D,gBAAU,QAAQ;AAClB,cAAQ,MAAM,4BAA4B,QAAQ;AAElD,aAAOA,cAAa;AAAA,QAClB,EAAE,OAAO,sCAAsC;AAAA,QAC/C,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBACP,OACA,aACA,OACA,aACA,kBACQ;AACR,QAAM,WAAqB,CAAC,KAAK,KAAK,EAAE;AAExC,MAAI,aAAa;AACf,aAAS,KAAK,WAAW;AAAA,EAC3B;AAEA,MAAI,MAAM,SAAS,GAAG;AACpB,aAAS,KAAK,iBAAiB,OAAO,gBAAgB,CAAC;AAEvD,UAAM,kBAAkB,eAAe,KAAK;AAC5C,QAAI,iBAAiB;AACnB,eAAS,KAAK,eAAe;AAAA,IAC/B;AAAA,EACF;AAEA,MAAI,YAAY,SAAS,GAAG;AAC1B,aAAS,KAAK,kBAAkB,WAAW,CAAC;AAAA,EAC9C;AAEA,SAAO,SAAS,KAAK,MAAM;AAC7B;AAEA,SAAS,iBACP,OACA,iBACQ;AACR,QAAM,gBAAgB,qBAAqB,KAAK;AAEhD,QAAM,aAAa,CAAC,QAAQ,OAAO;AACnC,aAAW,EAAE,MAAM,SAAS,KAAK,eAAe;AAC9C,eAAW,KAAK,GAAG,IAAI,IAAI,YAAY,MAAM,EAAE;AAC/C,eAAW,KAAK,SAAS;AAAA,EAC3B;AAEA,QAAM,OAAO,MAAM,IAAI,CAAC,SAAS;AAC/B,UAAM,YAAY,gBAAgB,MAAM,eAAe;AACvD,UAAM,OAAO,CAAC,KAAK,MAAM,SAAS;AAElC,UAAM,iBAAiB,IAAI;AAAA,MACzB,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,MAAM,OAAO,CAAC;AAAA,IACxD;AAEA,eAAW,EAAE,KAAK,KAAK,eAAe;AACpC,YAAM,UAAU,eAAe,IAAI,IAAI;AAEvC,UAAI,CAAC,SAAS;AACZ,aAAK,KAAK,UAAK,QAAG;AAClB;AAAA,MACF;AAEA,UAAI,QAAQ,WAAW;AACrB,aAAK,KAAK,aAAa,QAAG;AAC1B;AAAA,MACF;AAEA,YAAM,WAAW,QAAQ;AACzB,WAAK,KAAK,WAAW,aAAa,QAAQ,IAAI,QAAG;AAEjD,YAAM,eACJ,QAAQ,kBAAkB,QAAQ,qBAAqB,SACnD,gBAAgB,QAAQ,gBAAgB,IACxC;AACN,WAAK,KAAK,YAAY;AAAA,IACxB;AAEA,WAAO;AAAA,EACT,CAAC;AAED,QAAM,YAAY,WAAW,IAAI,MAAM,KAAK;AAC5C,QAAM,QAAQ;AAAA,IACZ,KAAK,WAAW,KAAK,KAAK,CAAC;AAAA,IAC3B,KAAK,UAAU,KAAK,KAAK,CAAC;AAAA,IAC1B,GAAG,KAAK,IAAI,CAAC,SAAS,KAAK,KAAK,KAAK,KAAK,CAAC,IAAI;AAAA,EACjD;AAEA,QAAM,YAAsB,CAAC;AAE7B,QAAM,aAAa,MAAM;AAAA,IAAO,CAAC,SAC/B,KAAK,OAAO,KAAK,CAAC,UAAU,MAAM,YAAY,CAAC;AAAA,EACjD;AACA,aAAW,QAAQ,YAAY;AAC7B,UAAM,aAAa,KAAK,OAAO,KAAK,CAAC,UAAU,MAAM,YAAY,CAAC;AAClE,cAAU;AAAA,MACR,IAAI,KAAK,IAAI,oBAAoB,WAAW,SAAS;AAAA,IACvD;AAAA,EACF;AAEA,MAAI,cAAc,SAAS,GAAG;AAC5B,cAAU;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO,UAAU,SAAS,IACtB;AAAA;AAAA,EAAe,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA,EAAO,UAAU,KAAK,MAAM,CAAC,KAC5D;AAAA;AAAA,EAAe,MAAM,KAAK,IAAI,CAAC;AACrC;AAEA,SAAS,gBACP,MACA,iBACQ;AACR,MAAI,KAAK,OAAQ,QAAO;AAExB,QAAM,SAAS,kBACX,KAAK,OAAO;AAAA,IAAO,CAAC,UAClB,gBAAgB,SAAS,MAAM,eAAe;AAAA,EAChD,IACA,KAAK;AAET,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,SAAO,OACJ,IAAI,CAAC,UAAU;AACd,QAAI,MAAM,UAAU,EAAG,QAAO;AAC9B,WAAO,GAAG,sBAAsB,MAAM,KAAK,CAAC,GAAG,eAAe,MAAM,eAAe,CAAC;AAAA,EACtF,CAAC,EACA,KAAK,KAAK;AACf;AAEA,SAAS,eAAe,OAA8B;AACpD,QAAM,kBAAkB,oBAAI,IAAY;AACxC,aAAW,QAAQ,OAAO;AACxB,eAAW,WAAW,KAAK,UAAU;AACnC,UAAI,QAAQ,SAAS,WAAW;AAC9B,wBAAgB,IAAI,QAAQ,IAAI;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,gBAAgB,SAAS,EAAG,QAAO;AAEvC,QAAM,eAAe,oBAAI,IAAY;AACrC,aAAW,QAAQ,iBAAiB;AAClC,UAAM,qBAAqB,MAAM,MAAM,CAAC,SAAS;AAC/C,YAAM,UAAU,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACzD,aAAO,SAAS;AAAA,IAClB,CAAC;AACD,QAAI,oBAAoB;AACtB,mBAAa,IAAI,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,eAAyB,CAAC;AAChC,aAAW,QAAQ,OAAO;AACxB,UAAM,iBAAiB,KAAK,SACzB;AAAA,MACC,CAAC,YACC,QAAQ,SAAS,aACjB,QAAQ,WACR,CAAC,aAAa,IAAI,QAAQ,IAAI;AAAA,IAClC,EACC,IAAI,CAAC,YAAY,QAAQ,IAAI;AAEhC,QAAI,eAAe,SAAS,GAAG;AAC7B,mBAAa,KAAK,OAAO,KAAK,IAAI,OAAO,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,IACtE;AAAA,EACF;AAEA,QAAM,iBAAiB,MAAM,CAAC,GAAG,SAC9B;AAAA,IACC,CAAC,YAAY,QAAQ,SAAS,aAAa,aAAa,IAAI,QAAQ,IAAI;AAAA,EAC1E,EACC,IAAI,CAAC,YAAY,QAAQ,IAAI;AAEhC,QAAM,QAAkB,CAAC;AAEzB,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,KAAK,aAAa,KAAK,IAAI,CAAC;AAAA,EACpC;AAEA,MAAI,kBAAkB,eAAe,SAAS,GAAG;AAC/C,UAAM,KAAK,sBAAsB,eAAe,KAAK,IAAI,CAAC,GAAG;AAAA,EAC/D;AAEA,SAAO,MAAM,SAAS,IAAI;AAAA;AAAA,EAAkB,MAAM,KAAK,MAAM,CAAC,KAAK;AACrE;AAEA,SAAS,kBAAkB,OAA6B;AACtD,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,GAAG,MAAM;AAAA,MACP,CAAC,SACC,KAAK,KAAK,IAAI,MAAM,aAAa,KAAK,OAAO,CAAC,cAAc,sBAAsB,KAAK,KAAK,CAAC;AAAA,IACjG;AAAA,EACF;AAEA,SAAO;AAAA;AAAA,EAAsB,MAAM,KAAK,IAAI,CAAC;AAC/C;AAQA,SAAS,qBAAqB,OAAqC;AACjE,QAAM,OAAO,oBAAI,IAAgC;AACjD,aAAW,QAAQ,OAAO;AACxB,eAAW,WAAW,KAAK,UAAU;AACnC,WACG,QAAQ,SAAS,WAAW,QAAQ,SAAS,YAC9C,CAAC,KAAK,IAAI,QAAQ,IAAI,KACtB,aAAa,OAAO,GACpB;AACA,aAAK,IAAI,QAAQ,MAAM;AAAA,UACrB,MAAM,QAAQ;AAAA,UACd,MAAM,QAAQ;AAAA,UACd,UAAU,QAAQ;AAAA,QACpB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,KAAK,KAAK,OAAO,CAAC;AACjC;AAEA,SAAS,aAAa,SAA+B;AACnD,SACE,QAAQ,cAAc,QACrB,QAAQ,mBAAmB,UAAa,QAAQ,iBAAiB,KACjE,QAAQ,mBAAmB,QAAQ,QAAQ,qBAAqB;AAErE;AAEA,SAAS,sBAAsB,OAAuB;AACpD,MAAI,UAAU,EAAG,QAAO;AACxB,QAAM,UAAU,QAAQ;AACxB,QAAM,CAAC,OAAO,OAAO,IAAI,QAAQ,QAAQ,CAAC,EAAE,MAAM,GAAG;AACrD,SAAO,IAAI,OAAO,KAAK,EAAE,eAAe,OAAO,CAAC,IAAI,OAAO;AAC7D;AAEA,SAAS,gBAAgB,MAAsB;AAC7C,QAAM,UAAU,OAAO;AAEvB,MAAI,YAAY,KAAK,MAAM,OAAO,GAAG;AACnC,WAAO,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC/B;AAEA,QAAM,YAAY,QAAQ,QAAQ,CAAC,EAAE,QAAQ,OAAO,EAAE;AACtD,QAAM,gBAAgB,UAAU,MAAM,GAAG,EAAE,CAAC,GAAG,UAAU;AACzD,MAAI,gBAAgB,GAAG;AACrB,WAAO,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC/B;AAEA,SAAO,IAAI,SAAS;AACtB;AAEA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,eAAe,OAAO;AACjC;;;ACtVA,SAAS,YAAY,sBAAsB;AAC3C,SAA2B,gBAAAC,qBAAoB;AA2BxC,IAAM,WAAW,CAAC,WAA2B;AAClD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,WAAW,IAAI,eAAe;AAEpC,SAAO,OAAO,YAAyB;AACrC,QAAI;AAEF,YAAM,UAAU,MAAM,QAAQ,KAAK;AAGnC,YAAM,YAAY,QAAQ,QAAQ,IAAI,oBAAoB;AAG1D,YAAM,UAAU,SAAS,OAAO;AAAA,QAC9B,SAAS;AAAA,QACT;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAED,UAAI,CAAC,SAAS;AACZ,gBAAQ,MAAM,oCAAoC;AAClD,eAAOA,cAAa;AAAA,UAClB,EAAE,UAAU,OAAO,OAAO,oBAAoB;AAAA,UAC9C,EAAE,QAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AAGA,UAAI;AACJ,UAAI;AACF,kBAAU,KAAK,MAAM,OAAO;AAAA,MAC9B,SAAS,YAAY;AACnB,gBAAQ,MAAM,6CAA6C,UAAU;AACrE,YAAI,SAAS;AACX,gBAAM;AAAA,YACJ,sBAAsB,QAClB,aACA,IAAI,MAAM,iCAAiC;AAAA,YAC/C;AAAA,UACF;AAAA,QACF;AACA,eAAOA,cAAa;AAAA,UAClB,EAAE,UAAU,OAAO,OAAO,kBAAkB;AAAA,UAC5C,EAAE,QAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AAGA,YAAM,WAA4B,CAAC;AAGnC,UAAI,WAAW;AACb,iBAAS,KAAK,UAAU,OAAO,CAAC;AAAA,MAClC;AAGA,cAAQ,QAAQ,OAAO;AAAA,QACrB,KAAK;AACH,cAAI,yBAAyB;AAC3B,qBAAS,KAAK,wBAAwB,OAAO,CAAC;AAAA,UAChD;AACA;AAAA,QAEF,KAAK;AACH,cAAI,wBAAwB;AAC1B,qBAAS,KAAK,uBAAuB,OAAO,CAAC;AAAA,UAC/C;AACA;AAAA,QAEF,KAAK;AACH,cAAI,uBAAuB;AACzB,qBAAS,KAAK,sBAAsB,OAAO,CAAC;AAAA,UAC9C;AACA;AAAA,QAEF,KAAK;AACH,cAAI,uBAAuB;AACzB,qBAAS,KAAK,sBAAsB,OAAO,CAAC;AAAA,UAC9C;AACA;AAAA,QAEF,KAAK;AACH,cAAI,2BAA2B;AAC7B,qBAAS,KAAK,0BAA0B,OAAO,CAAC;AAAA,UAClD;AACA;AAAA,QAEF,KAAK;AACH,cAAI,mBAAmB;AACrB,qBAAS,KAAK,kBAAkB,OAAO,CAAC;AAAA,UAC1C;AACA;AAAA,QAEF,KAAK;AACH,cAAI,iBAAiB;AACnB,qBAAS,KAAK,gBAAgB,OAAO,CAAC;AAAA,UACxC;AACA;AAAA,QAEF,KAAK;AACH,cAAI,kBAAkB;AACpB,qBAAS,KAAK,iBAAiB,OAAO,CAAC;AAAA,UACzC;AACA;AAAA,QAEF;AACE,kBAAQ,IAAI,qCAAqC,QAAQ,KAAK,EAAE;AAAA,MACpE;AAGA,UAAI;AACF,cAAM,QAAQ,IAAI,QAAQ;AAAA,MAC5B,SAAS,cAAc;AACrB,gBAAQ;AAAA,UACN;AAAA,UACA,wBAAwB,QAAQ,aAAa,UAAU;AAAA,QACzD;AACA,YAAI,SAAS;AACX,gBAAM;AAAA,YACJ,wBAAwB,QACpB,eACA,IAAI,MAAM,0BAA0B;AAAA,YACxC;AAAA,UACF;AAAA,QACF;AAEA,eAAOA,cAAa;AAAA,UAClB,EAAE,UAAU,MAAM,SAAS,iBAAiB;AAAA,UAC5C,EAAE,QAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AAGA,aAAOA,cAAa,KAAK,EAAE,UAAU,KAAK,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9D,SAAS,OAAO;AACd,cAAQ,MAAM,sCAAsC,KAAK;AACzD,UAAI,SAAS;AACX,YAAI;AACF,gBAAM;AAAA,YACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,kBAAkB;AAAA,YAC7D;AAAA,UACF;AAAA,QACF,SAAS,mBAAmB;AAC1B,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,aAAOA,cAAa;AAAA,QAClB,EAAE,UAAU,OAAO,OAAO,iBAAiB;AAAA,QAC3C,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;;;AHlKA,oBAAoB,gBAAgB,OAAe;","names":["Commet","NextResponse","NextResponse"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commet/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Next.js integration for Commet billing platform",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"author": "Commet Team",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@commet/node": "
|
|
29
|
+
"@commet/node": "5.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "24.12.4",
|