@polar-sh/better-auth
Version:
Polar integration for better-auth
734 lines (725 loc) • 23.6 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
checkout: () => checkout,
polar: () => polar,
polarClient: () => polarClient,
portal: () => portal,
usage: () => usage,
webhooks: () => webhooks
});
module.exports = __toCommonJS(index_exports);
// src/hooks/customer.ts
var import_api = require("better-auth/api");
var onUserCreate = (options) => async (user, ctx) => {
if (ctx && options.createCustomerOnSignUp) {
try {
const params = options.getCustomerCreateParams ? await options.getCustomerCreateParams({
user
}) : {};
const { result: existingCustomers } = await options.client.customers.list({ email: user.email });
const existingCustomer = existingCustomers.items[0];
if (existingCustomer) {
if (existingCustomer.externalId !== user.id) {
await options.client.customers.update({
id: existingCustomer.id,
customerUpdate: {
externalId: user.id,
...params
}
});
}
} else {
const customer = await options.client.customers.create({
...params,
email: user.email,
name: user.name,
externalId: user.id
});
console.log(customer);
}
} catch (e) {
if (e instanceof Error) {
throw new import_api.APIError("INTERNAL_SERVER_ERROR", {
message: `Polar customer creation failed. Error: ${e.message}`
});
}
throw new import_api.APIError("INTERNAL_SERVER_ERROR", {
message: `Polar customer creation failed. Error: ${e}`
});
}
}
};
var onUserUpdate = (options) => async (user, ctx) => {
if (ctx && options.createCustomerOnSignUp) {
try {
await options.client.customers.updateExternal({
externalId: user.id,
customerUpdateExternalID: {
email: user.email,
name: user.name
}
});
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar customer update failed. Error: ${e.message}`
);
} else {
ctx.context.logger.error(`Polar customer update failed. Error: ${e}`);
}
}
}
};
// src/client.ts
var polarClient = () => {
return {
id: "polar-client",
$InferServerPlugin: {}
};
};
// src/plugins/portal.ts
var import_api2 = require("better-auth/api");
var import_api3 = require("better-auth/api");
var import_plugins = require("better-auth/plugins");
var import_zod = require("zod");
var portal = () => (polar2) => {
return {
portal: (0, import_plugins.createAuthEndpoint)(
"/customer/portal",
{
method: "GET",
use: [import_api3.sessionMiddleware]
},
async (ctx) => {
if (!ctx.context.session?.user.id) {
throw new import_api2.APIError("BAD_REQUEST", {
message: "User not found"
});
}
try {
const customerSession = await polar2.customerSessions.create({
externalCustomerId: ctx.context.session?.user.id
});
return ctx.json({
url: customerSession.customerPortalUrl,
redirect: true
});
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar customer portal creation failed. Error: ${e.message}`
);
}
throw new import_api2.APIError("INTERNAL_SERVER_ERROR", {
message: "Customer portal creation failed"
});
}
}
),
state: (0, import_plugins.createAuthEndpoint)(
"/customer/state",
{
method: "GET",
use: [import_api3.sessionMiddleware]
},
async (ctx) => {
if (!ctx.context.session.user.id) {
throw new import_api2.APIError("BAD_REQUEST", {
message: "User not found"
});
}
try {
const state = await polar2.customers.getStateExternal({
externalId: ctx.context.session?.user.id
});
return ctx.json(state);
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar subscriptions list failed. Error: ${e.message}`
);
}
throw new import_api2.APIError("INTERNAL_SERVER_ERROR", {
message: "Subscriptions list failed"
});
}
}
),
benefits: (0, import_plugins.createAuthEndpoint)(
"/customer/benefits/list",
{
method: "GET",
query: import_zod.z.object({
page: import_zod.z.coerce.number().optional(),
limit: import_zod.z.coerce.number().optional()
}).optional(),
use: [import_api3.sessionMiddleware]
},
async (ctx) => {
if (!ctx.context.session.user.id) {
throw new import_api2.APIError("BAD_REQUEST", {
message: "User not found"
});
}
try {
const customerSession = await polar2.customerSessions.create({
externalCustomerId: ctx.context.session?.user.id
});
const benefits = await polar2.customerPortal.benefitGrants.list(
{ customerSession: customerSession.token },
{
page: ctx.query?.page,
limit: ctx.query?.limit
}
);
return ctx.json(benefits);
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar benefits list failed. Error: ${e.message}`
);
}
throw new import_api2.APIError("INTERNAL_SERVER_ERROR", {
message: "Benefits list failed"
});
}
}
),
subscriptions: (0, import_plugins.createAuthEndpoint)(
"/customer/subscriptions/list",
{
method: "GET",
query: import_zod.z.object({
referenceId: import_zod.z.string().optional(),
page: import_zod.z.coerce.number().optional(),
limit: import_zod.z.coerce.number().optional(),
active: import_zod.z.coerce.boolean().optional()
}).optional(),
use: [import_api3.sessionMiddleware]
},
async (ctx) => {
if (!ctx.context.session.user.id) {
throw new import_api2.APIError("BAD_REQUEST", {
message: "User not found"
});
}
if (ctx.query?.referenceId) {
try {
const subscriptions = await polar2.subscriptions.list({
page: ctx.query?.page,
limit: ctx.query?.limit,
active: ctx.query?.active,
metadata: {
referenceId: ctx.query?.referenceId
}
});
return ctx.json(subscriptions);
} catch (e) {
console.log(e);
if (e instanceof Error) {
ctx.context.logger.error(
`Polar subscriptions list with referenceId failed. Error: ${e.message}`
);
}
throw new import_api2.APIError("INTERNAL_SERVER_ERROR", {
message: "Subscriptions list with referenceId failed"
});
}
}
try {
const customerSession = await polar2.customerSessions.create({
externalCustomerId: ctx.context.session?.user.id
});
const subscriptions = await polar2.customerPortal.subscriptions.list(
{ customerSession: customerSession.token },
{
page: ctx.query?.page,
limit: ctx.query?.limit,
active: ctx.query?.active
}
);
return ctx.json(subscriptions);
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar subscriptions list failed. Error: ${e.message}`
);
}
throw new import_api2.APIError("INTERNAL_SERVER_ERROR", {
message: "Polar subscriptions list failed"
});
}
}
),
orders: (0, import_plugins.createAuthEndpoint)(
"/customer/orders/list",
{
method: "GET",
query: import_zod.z.object({
page: import_zod.z.coerce.number().optional(),
limit: import_zod.z.coerce.number().optional(),
productBillingType: import_zod.z.enum(["recurring", "one_time"]).optional()
}).optional(),
use: [import_api3.sessionMiddleware]
},
async (ctx) => {
if (!ctx.context.session.user.id) {
throw new import_api2.APIError("BAD_REQUEST", {
message: "User not found"
});
}
try {
const customerSession = await polar2.customerSessions.create({
externalCustomerId: ctx.context.session?.user.id
});
const orders = await polar2.customerPortal.orders.list(
{ customerSession: customerSession.token },
{
page: ctx.query?.page,
limit: ctx.query?.limit,
productBillingType: ctx.query?.productBillingType
}
);
return ctx.json(orders);
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar orders list failed. Error: ${e.message}`
);
}
throw new import_api2.APIError("INTERNAL_SERVER_ERROR", {
message: "Orders list failed"
});
}
}
)
};
};
// src/plugins/checkout.ts
var import_api4 = require("better-auth/api");
var import_plugins2 = require("better-auth/plugins");
var import_zod2 = require("zod");
var checkout = (checkoutOptions = {}) => (polar2) => {
return {
checkout: (0, import_plugins2.createAuthEndpoint)(
"/checkout",
{
method: "POST",
body: import_zod2.z.object({
products: import_zod2.z.union([import_zod2.z.array(import_zod2.z.string()), import_zod2.z.string()]).optional(),
slug: import_zod2.z.string().optional(),
referenceId: import_zod2.z.string().optional(),
customFieldData: import_zod2.z.record(
import_zod2.z.string(),
import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number(), import_zod2.z.boolean()])
).optional(),
metadata: import_zod2.z.record(
import_zod2.z.string(),
import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number(), import_zod2.z.boolean()])
).optional()
})
},
async (ctx) => {
const session = await (0, import_api4.getSessionFromCtx)(ctx);
let productIds = [];
if (ctx.body.slug) {
const resolvedProducts = await (typeof checkoutOptions.products === "function" ? checkoutOptions.products() : checkoutOptions.products);
const productId = resolvedProducts?.find(
(product) => product.slug === ctx.body.slug
)?.productId;
if (!productId) {
throw new import_api4.APIError("BAD_REQUEST", {
message: "Product not found"
});
}
productIds = [productId];
} else {
productIds = Array.isArray(ctx.body.products) ? ctx.body.products.filter((id) => id !== void 0) : [ctx.body.products].filter((id) => id !== void 0);
}
if (checkoutOptions.authenticatedUsersOnly && !session?.user.id) {
throw new import_api4.APIError("UNAUTHORIZED", {
message: "You must be logged in to checkout"
});
}
try {
const checkout2 = await polar2.checkouts.create({
externalCustomerId: session?.user.id,
products: productIds,
successUrl: checkoutOptions.successUrl ? new URL(
checkoutOptions.successUrl,
ctx.request?.url
).toString() : void 0,
metadata: ctx.body.referenceId ? {
referenceId: ctx.body.referenceId,
...ctx.body.metadata
} : ctx.body.metadata,
customFieldData: ctx.body.customFieldData
});
const redirectUrl = new URL(checkout2.url);
if (checkoutOptions.theme) {
redirectUrl.searchParams.set("theme", checkoutOptions.theme);
}
return ctx.json({
url: redirectUrl.toString(),
redirect: true
});
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar checkout creation failed. Error: ${e.message}`
);
}
throw new import_api4.APIError("INTERNAL_SERVER_ERROR", {
message: "Checkout creation failed"
});
}
}
)
};
};
// src/plugins/usage.ts
var import_api5 = require("better-auth/api");
var import_zod3 = require("zod");
var usage = (_usageOptions) => (polar2) => {
return {
meters: (0, import_api5.createAuthEndpoint)(
"/usage/meters/list",
{
method: "GET",
use: [import_api5.sessionMiddleware],
query: import_zod3.z.object({
page: import_zod3.z.coerce.number().optional(),
limit: import_zod3.z.coerce.number().optional()
})
},
async (ctx) => {
if (!ctx.context.session.user.id) {
throw new import_api5.APIError("BAD_REQUEST", {
message: "User not found"
});
}
try {
const customerSession = await polar2.customerSessions.create({
externalCustomerId: ctx.context.session.user.id
});
const customerMeters = await polar2.customerPortal.customerMeters.list(
{ customerSession: customerSession.token },
{
page: ctx.query?.page,
limit: ctx.query?.limit
}
);
return ctx.json(customerMeters);
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar meters list failed. Error: ${e.message}`
);
}
throw new import_api5.APIError("INTERNAL_SERVER_ERROR", {
message: "Meters list failed"
});
}
}
),
ingestion: (0, import_api5.createAuthEndpoint)(
"/usage/ingest",
{
method: "POST",
body: import_zod3.z.object({
event: import_zod3.z.string(),
metadata: import_zod3.z.record(
import_zod3.z.string(),
import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number(), import_zod3.z.boolean()])
)
}),
use: [import_api5.sessionMiddleware]
},
async (ctx) => {
if (!ctx.context.session.user.id) {
throw new import_api5.APIError("BAD_REQUEST", {
message: "User not found"
});
}
try {
const ingestion = await polar2.events.ingest({
events: [
{
name: ctx.body.event,
metadata: ctx.body.metadata,
externalCustomerId: ctx.context.session.user.id
}
]
});
return ctx.json(ingestion);
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar ingestion failed. Error: ${e.message}`
);
}
throw new import_api5.APIError("INTERNAL_SERVER_ERROR", {
message: "Ingestion failed"
});
}
}
)
};
};
// ../adapter-utils/dist/index.js
var handleWebhookPayload = async (payload, { webhookSecret, entitlements, onPayload, ...eventHandlers }) => {
const promises = [];
if (onPayload) {
promises.push(onPayload(payload));
}
switch (payload.type) {
case "checkout.created":
if (eventHandlers.onCheckoutCreated) {
promises.push(eventHandlers.onCheckoutCreated(payload));
}
break;
case "checkout.updated":
if (eventHandlers.onCheckoutUpdated) {
promises.push(eventHandlers.onCheckoutUpdated(payload));
}
break;
case "order.created":
if (eventHandlers.onOrderCreated) {
promises.push(eventHandlers.onOrderCreated(payload));
}
break;
case "order.updated":
if (eventHandlers.onOrderUpdated) {
promises.push(eventHandlers.onOrderUpdated(payload));
}
break;
case "order.paid":
if (eventHandlers.onOrderPaid) {
promises.push(eventHandlers.onOrderPaid(payload));
}
break;
case "subscription.created":
if (eventHandlers.onSubscriptionCreated) {
promises.push(eventHandlers.onSubscriptionCreated(payload));
}
break;
case "subscription.updated":
if (eventHandlers.onSubscriptionUpdated) {
promises.push(eventHandlers.onSubscriptionUpdated(payload));
}
break;
case "subscription.active":
if (eventHandlers.onSubscriptionActive) {
promises.push(eventHandlers.onSubscriptionActive(payload));
}
break;
case "subscription.canceled":
if (eventHandlers.onSubscriptionCanceled) {
promises.push(eventHandlers.onSubscriptionCanceled(payload));
}
break;
case "subscription.revoked":
if (eventHandlers.onSubscriptionRevoked) {
promises.push(eventHandlers.onSubscriptionRevoked(payload));
}
break;
case "product.created":
if (eventHandlers.onProductCreated) {
promises.push(eventHandlers.onProductCreated(payload));
}
break;
case "product.updated":
if (eventHandlers.onProductUpdated) {
promises.push(eventHandlers.onProductUpdated(payload));
}
break;
case "organization.updated":
if (eventHandlers.onOrganizationUpdated) {
promises.push(eventHandlers.onOrganizationUpdated(payload));
}
break;
case "benefit.created":
if (eventHandlers.onBenefitCreated) {
promises.push(eventHandlers.onBenefitCreated(payload));
}
break;
case "benefit.updated":
if (eventHandlers.onBenefitUpdated) {
promises.push(eventHandlers.onBenefitUpdated(payload));
}
break;
case "benefit_grant.created":
if (eventHandlers.onBenefitGrantCreated) {
promises.push(eventHandlers.onBenefitGrantCreated(payload));
}
break;
case "benefit_grant.updated":
if (eventHandlers.onBenefitGrantUpdated) {
promises.push(eventHandlers.onBenefitGrantUpdated(payload));
}
break;
case "benefit_grant.revoked":
if (eventHandlers.onBenefitGrantRevoked) {
promises.push(eventHandlers.onBenefitGrantRevoked(payload));
}
break;
case "customer.created":
if (eventHandlers.onCustomerCreated) {
promises.push(eventHandlers.onCustomerCreated(payload));
}
break;
case "customer.updated":
if (eventHandlers.onCustomerUpdated) {
promises.push(eventHandlers.onCustomerUpdated(payload));
}
break;
case "customer.deleted":
if (eventHandlers.onCustomerDeleted) {
promises.push(eventHandlers.onCustomerDeleted(payload));
}
break;
case "customer.state_changed":
if (eventHandlers.onCustomerStateChanged) {
promises.push(eventHandlers.onCustomerStateChanged(payload));
}
break;
}
switch (payload.type) {
case "benefit_grant.created":
case "benefit_grant.revoked":
if (entitlements) {
for (const handler of entitlements.handlers) {
promises.push(handler(payload));
}
}
}
return Promise.all(promises);
};
// src/plugins/webhooks.ts
var import_webhooks = require("@polar-sh/sdk/webhooks.js");
var import_api6 = require("better-auth/api");
var webhooks = (options) => (_polar) => {
return {
polarWebhooks: (0, import_api6.createAuthEndpoint)(
"/polar/webhooks",
{
method: "POST",
metadata: {
isAction: false
},
cloneRequest: true
},
async (ctx) => {
const { secret, ...eventHandlers } = options;
if (!ctx.request?.body) {
throw new import_api6.APIError("INTERNAL_SERVER_ERROR");
}
const buf = await ctx.request.text();
let event;
try {
if (!secret) {
throw new import_api6.APIError("INTERNAL_SERVER_ERROR", {
message: "Polar webhook secret not found"
});
}
const headers = {
"webhook-id": ctx.request.headers.get("webhook-id"),
"webhook-timestamp": ctx.request.headers.get(
"webhook-timestamp"
),
"webhook-signature": ctx.request.headers.get(
"webhook-signature"
)
};
event = (0, import_webhooks.validateEvent)(buf, headers, secret);
} catch (err) {
if (err instanceof Error) {
ctx.context.logger.error(`${err.message}`);
throw new import_api6.APIError("BAD_REQUEST", {
message: `Webhook Error: ${err.message}`
});
}
throw new import_api6.APIError("BAD_REQUEST", {
message: `Webhook Error: ${err}`
});
}
try {
await handleWebhookPayload(event, {
webhookSecret: secret,
...eventHandlers
});
} catch (e) {
if (e instanceof Error) {
ctx.context.logger.error(
`Polar webhook failed. Error: ${e.message}`
);
} else {
ctx.context.logger.error(`Polar webhook failed. Error: ${e}`);
}
throw new import_api6.APIError("BAD_REQUEST", {
message: "Webhook error: See server logs for more information."
});
}
return ctx.json({ received: true });
}
)
};
};
// src/index.ts
var polar = (options) => {
const plugins = options.use.map((use) => use(options.client)).reduce((acc, plugin) => {
Object.assign(acc, plugin);
return acc;
}, {});
return {
id: "polar",
endpoints: {
...plugins
},
init() {
return {
options: {
databaseHooks: {
user: {
create: {
after: onUserCreate(options)
},
update: {
after: onUserUpdate(options)
}
}
}
}
};
}
};
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
checkout,
polar,
polarClient,
portal,
usage,
webhooks
});
//# sourceMappingURL=index.cjs.map