This commit is contained in:
Shawn Erquhart
2025-02-21 19:54:26 -05:00
parent 55ad3dd679
commit 7592f9545d
3 changed files with 44 additions and 17 deletions

View File

@@ -41,9 +41,10 @@ const currentUser = async (ctx: QueryCtx) => {
const subscription = await polar.getCurrentSubscription(ctx, {
userId: user._id,
});
const isPremiumPlus = subscription?.product?.id === products.premiumPlus;
const isPremiumPlus =
subscription?.product?.id === polar.products.premiumPlus;
const isPremium =
isPremiumPlus || subscription?.product?.id === products.premium;
isPremiumPlus || subscription?.product?.id === polar.products.premium;
return {
...user,
isPremium,

View File

@@ -19,6 +19,13 @@ export function BillingSettings({
: isPremium
? "Premium"
: "Free";
const currentPrice = isPremiumPlus
? "$20/month or $200/year"
: isPremium
? "$10/month or $100/year"
: "Free";
const features = isPremiumPlus
? ["Unlimited todos", "No ads", "Priority support", "Advanced analytics"]
: isPremium
@@ -61,9 +68,21 @@ export function BillingSettings({
<div className="mt-4">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-lg font-medium">Current Plan:</h3>
<div className="flex items-center gap-2">
<span className="px-3 py-1 rounded-full text-sm font-medium bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200">
{currentPlan}
</span>
{currentPrice !== "Free" && (
<div className="flex flex-col text-sm">
<span className="font-medium text-gray-600 dark:text-gray-400">
{isPremiumPlus ? "$20/month" : "$10/month"}
</span>
<span className="text-xs text-gray-500 dark:text-gray-500">
or {isPremiumPlus ? "$200/year" : "$100/year"}
</span>
</div>
)}
</div>
</div>
<ul className="mt-4 space-y-2">
{features.map((feature) => (

View File

@@ -37,24 +37,31 @@ export type SubscriptionHandler = FunctionReference<
{ subscription: Subscription }
>;
export type CheckoutApi<DataModel extends GenericDataModel> = ApiFromModules<{
checkout: ReturnType<Polar<DataModel>["checkoutApi"]>;
export type CheckoutApi<
DataModel extends GenericDataModel,
Products extends Record<string, string>,
> = ApiFromModules<{
checkout: ReturnType<Polar<DataModel, Products>["checkoutApi"]>;
}>["checkout"];
export class Polar<DataModel extends GenericDataModel> {
private polar: PolarSdk;
export class Polar<
DataModel extends GenericDataModel,
Products extends Record<string, string>,
> {
public sdk: PolarSdk;
public products: Products;
constructor(
public component: ComponentApi,
private config: {
products: Record<string, string>;
products: Products;
getUserInfo: (ctx: RunQueryCtx) => Promise<{
userId: string;
email: string;
}>;
}
) {
this.polar = new PolarSdk({
this.products = config.products;
this.sdk = new PolarSdk({
accessToken: process.env["POLAR_ORGANIZATION_TOKEN"] ?? "",
server:
(process.env["POLAR_SERVER"] as "sandbox" | "production") ?? "sandbox",
@@ -81,7 +88,7 @@ export class Polar<DataModel extends GenericDataModel> {
const customerId =
dbCustomer?.id ||
(
await this.polar.customers.create({
await this.sdk.customers.create({
email,
metadata: {
userId,
@@ -94,7 +101,7 @@ export class Polar<DataModel extends GenericDataModel> {
userId,
});
}
return this.polar.checkouts.create({
return this.sdk.checkouts.create({
allowDiscountCodes: true,
products: [productId],
customerId,
@@ -114,7 +121,7 @@ export class Polar<DataModel extends GenericDataModel> {
throw new Error("Customer not found");
}
const session = await this.polar.customerSessions.create({
const session = await this.sdk.customerSessions.create({
customerId: customer.id,
});
@@ -150,7 +157,7 @@ export class Polar<DataModel extends GenericDataModel> {
if (subscription.productId === productId) {
throw new Error("Subscription already on this product");
}
await this.polar.subscriptions.update({
await this.sdk.subscriptions.update({
id: subscription.id,
subscriptionUpdate: {
productId,
@@ -169,7 +176,7 @@ export class Polar<DataModel extends GenericDataModel> {
if (subscription.status !== "active") {
throw new Error("Subscription is not active");
}
await this.polar.subscriptions.update({
await this.sdk.subscriptions.update({
id: subscription.id,
subscriptionUpdate: {
cancelAtPeriodEnd: revokeImmediately ? null : true,