feat(subscriptions): added polar as payment provider for subscriptions

* feature(dashboard): add polar / subscription

* wip(payments): manage subscription

* wip(payments): add free product, faq and some other improvements

* fix(root): change node to bundler in tsconfig

* wip(payments): display current subscription

* feat(dashboard): schedule project for deletion

* wip(payments): support custom products/subscriptions

* wip(payments): fix polar scripts

* wip(payments): add json package to dockerfiles
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-02-26 11:24:00 +01:00
committed by GitHub
parent 86bf9dd064
commit 168ebc3430
105 changed files with 3395 additions and 463 deletions

View File

@@ -0,0 +1,112 @@
// src/polar.ts
import { Polar } from '@polar-sh/sdk';
export {
validateEvent as validatePolarEvent,
WebhookVerificationError as PolarWebhookVerificationError,
} from '@polar-sh/sdk/webhooks';
export type IPolarProduct = Awaited<ReturnType<typeof getProduct>>;
export type IPolarPrice = IPolarProduct['prices'][number];
export const polar = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN!,
server: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
});
export const getSuccessUrl = (
baseUrl: string,
organizationId: string,
projectId?: string,
) =>
projectId
? `${baseUrl}/${organizationId}/${projectId}/settings?tab=billing`
: `${baseUrl}/${organizationId}`;
export async function getProducts() {
const products = await polar.products.list({
limit: 100,
isArchived: false,
sorting: ['price_amount'],
});
return products.result.items.filter((product) => {
return product.metadata.custom !== true;
});
}
export async function getProduct(id: string) {
return polar.products.get({ id });
}
export async function createPortal({
customerId,
}: {
customerId: string;
}) {
return polar.customerSessions.create({
customerId,
});
}
export async function createCheckout({
priceId,
organizationId,
projectId,
user,
ipAddress,
}: {
priceId: string;
organizationId: string;
projectId?: string;
user: {
id: string;
firstName: string | null;
lastName: string | null;
email: string;
};
ipAddress: string;
}) {
return polar.checkouts.create({
productPriceId: priceId,
successUrl: getSuccessUrl(
process.env.NEXT_PUBLIC_DASHBOARD_URL!,
organizationId,
projectId,
),
customerEmail: user.email,
customerName: [user.firstName, user.lastName].filter(Boolean).join(' '),
customerIpAddress: ipAddress,
metadata: {
organizationId,
userId: user.id,
},
});
}
export function cancelSubscription(subscriptionId: string) {
return polar.subscriptions.update({
id: subscriptionId,
subscriptionUpdate: {
cancelAtPeriodEnd: true,
revoke: null,
},
});
}
export function reactivateSubscription(subscriptionId: string) {
return polar.subscriptions.update({
id: subscriptionId,
subscriptionUpdate: {
cancelAtPeriodEnd: false,
revoke: null,
},
});
}
export function changeSubscription(subscriptionId: string, productId: string) {
return polar.subscriptions.update({
id: subscriptionId,
subscriptionUpdate: {
productId,
},
});
}

View File

@@ -0,0 +1,18 @@
export type IPrice = {
price: number;
events: number;
};
export const PRICING: IPrice[] = [
{ price: 0, events: 5_000 },
{ price: 5, events: 10_000 },
{ price: 20, events: 100_000 },
{ price: 30, events: 250_000 },
{ price: 50, events: 500_000 },
{ price: 90, events: 1_000_000 },
{ price: 180, events: 2_500_000 },
{ price: 250, events: 5_000_000 },
{ price: 400, events: 10_000_000 },
// { price: 650, events: 20_000_000 },
// { price: 900, events: 30_000_000 },
];