mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-24 05:46:39 +00:00
Getting swtiching tiers to work
This commit is contained in:
@@ -97,6 +97,7 @@ export const subscriptionItems = pgTable("subscriptionItems", {
|
|||||||
}),
|
}),
|
||||||
planId: varchar("planId", { length: 255 }).notNull(),
|
planId: varchar("planId", { length: 255 }).notNull(),
|
||||||
priceId: varchar("priceId", { length: 255 }),
|
priceId: varchar("priceId", { length: 255 }),
|
||||||
|
featureId: varchar("featureId", { length: 255 }),
|
||||||
meterId: varchar("meterId", { length: 255 }),
|
meterId: varchar("meterId", { length: 255 }),
|
||||||
unitAmount: real("unitAmount"),
|
unitAmount: real("unitAmount"),
|
||||||
tiers: text("tiers"),
|
tiers: text("tiers"),
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const subscriptionItems = sqliteTable("subscriptionItems", {
|
|||||||
}),
|
}),
|
||||||
planId: text("planId").notNull(),
|
planId: text("planId").notNull(),
|
||||||
priceId: text("priceId"),
|
priceId: text("priceId"),
|
||||||
|
featureId: text("featureId"),
|
||||||
meterId: text("meterId"),
|
meterId: text("meterId"),
|
||||||
unitAmount: real("unitAmount"),
|
unitAmount: real("unitAmount"),
|
||||||
tiers: text("tiers"),
|
tiers: text("tiers"),
|
||||||
|
|||||||
@@ -116,6 +116,26 @@ export function getScaleFeaturePriceSet(): FeaturePriceSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
|
||||||
|
// Check all feature price sets
|
||||||
|
const allPriceSets = [
|
||||||
|
getHomeLabFeaturePriceSet(),
|
||||||
|
getStarterFeaturePriceSet(),
|
||||||
|
getScaleFeaturePriceSet()
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const priceSet of allPriceSets) {
|
||||||
|
const entry = (Object.entries(priceSet) as [FeatureId, string][]).find(
|
||||||
|
([_, price]) => price === priceId
|
||||||
|
);
|
||||||
|
if (entry) {
|
||||||
|
return entry[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getLineItems(
|
export async function getLineItems(
|
||||||
featurePriceSet: FeaturePriceSet,
|
featurePriceSet: FeaturePriceSet,
|
||||||
orgId: string,
|
orgId: string,
|
||||||
|
|||||||
@@ -206,7 +206,8 @@ export async function changeTier(
|
|||||||
// Keep the existing item unchanged if we can't find it
|
// Keep the existing item unchanged if we can't find it
|
||||||
return {
|
return {
|
||||||
id: stripeItem.id,
|
id: stripeItem.id,
|
||||||
price: stripeItem.price.id
|
price: stripeItem.price.id,
|
||||||
|
quantity: stripeItem.quantity
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,14 +217,16 @@ export async function changeTier(
|
|||||||
if (newPriceId) {
|
if (newPriceId) {
|
||||||
return {
|
return {
|
||||||
id: stripeItem.id,
|
id: stripeItem.id,
|
||||||
price: newPriceId
|
price: newPriceId,
|
||||||
|
quantity: stripeItem.quantity
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no mapping found, keep existing
|
// If no mapping found, keep existing
|
||||||
return {
|
return {
|
||||||
id: stripeItem.id,
|
id: stripeItem.id,
|
||||||
price: stripeItem.price.id
|
price: stripeItem.price.id,
|
||||||
|
quantity: stripeItem.quantity
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { getLicensePriceSet, LicenseId } from "@server/lib/billing/licenses";
|
|||||||
import { sendEmail } from "@server/emails";
|
import { sendEmail } from "@server/emails";
|
||||||
import EnterpriseEditionKeyGenerated from "@server/emails/templates/EnterpriseEditionKeyGenerated";
|
import EnterpriseEditionKeyGenerated from "@server/emails/templates/EnterpriseEditionKeyGenerated";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
import { getFeatureIdByPriceId } from "@server/lib/billing/features";
|
||||||
|
|
||||||
export async function handleSubscriptionCreated(
|
export async function handleSubscriptionCreated(
|
||||||
subscription: Stripe.Subscription
|
subscription: Stripe.Subscription
|
||||||
@@ -91,11 +92,15 @@ export async function handleSubscriptionCreated(
|
|||||||
name = product.name || null;
|
name = product.name || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the feature ID from the price ID
|
||||||
|
const featureId = getFeatureIdByPriceId(item.price.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stripeSubscriptionItemId: item.id,
|
stripeSubscriptionItemId: item.id,
|
||||||
subscriptionId: subscription.id,
|
subscriptionId: subscription.id,
|
||||||
planId: item.plan.id,
|
planId: item.plan.id,
|
||||||
priceId: item.price.id,
|
priceId: item.price.id,
|
||||||
|
featureId: featureId || null,
|
||||||
meterId: item.plan.meter,
|
meterId: item.plan.meter,
|
||||||
unitAmount: item.price.unit_amount || 0,
|
unitAmount: item.price.unit_amount || 0,
|
||||||
currentPeriodStart: item.current_period_start,
|
currentPeriodStart: item.current_period_start,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { getFeatureIdByMetricId } from "@server/lib/billing/features";
|
import { getFeatureIdByMetricId, getFeatureIdByPriceId } from "@server/lib/billing/features";
|
||||||
import stripe from "#private/lib/stripe";
|
import stripe from "#private/lib/stripe";
|
||||||
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
|
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
|
||||||
import { getSubType } from "./getSubType";
|
import { getSubType } from "./getSubType";
|
||||||
@@ -81,20 +81,40 @@ export async function handleSubscriptionUpdated(
|
|||||||
|
|
||||||
// Upsert subscription items
|
// Upsert subscription items
|
||||||
if (Array.isArray(fullSubscription.items?.data)) {
|
if (Array.isArray(fullSubscription.items?.data)) {
|
||||||
const itemsToUpsert = fullSubscription.items.data.map((item) => ({
|
// First, get existing items to preserve featureId when there's no match
|
||||||
stripeSubscriptionItemId: item.id,
|
const existingItems = await db
|
||||||
subscriptionId: subscription.id,
|
.select()
|
||||||
planId: item.plan.id,
|
.from(subscriptionItems)
|
||||||
priceId: item.price.id,
|
.where(eq(subscriptionItems.subscriptionId, subscription.id));
|
||||||
meterId: item.plan.meter,
|
|
||||||
unitAmount: item.price.unit_amount || 0,
|
const itemsToUpsert = fullSubscription.items.data.map((item) => {
|
||||||
currentPeriodStart: item.current_period_start,
|
// Try to get featureId from price
|
||||||
currentPeriodEnd: item.current_period_end,
|
let featureId: string | null = getFeatureIdByPriceId(item.price.id) || null;
|
||||||
tiers: item.price.tiers
|
|
||||||
? JSON.stringify(item.price.tiers)
|
// If no match, try to preserve existing featureId
|
||||||
: null,
|
if (!featureId) {
|
||||||
interval: item.plan.interval
|
const existingItem = existingItems.find(
|
||||||
}));
|
(ei) => ei.stripeSubscriptionItemId === item.id
|
||||||
|
);
|
||||||
|
featureId = existingItem?.featureId || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
stripeSubscriptionItemId: item.id,
|
||||||
|
subscriptionId: subscription.id,
|
||||||
|
planId: item.plan.id,
|
||||||
|
priceId: item.price.id,
|
||||||
|
featureId: featureId,
|
||||||
|
meterId: item.plan.meter,
|
||||||
|
unitAmount: item.price.unit_amount || 0,
|
||||||
|
currentPeriodStart: item.current_period_start,
|
||||||
|
currentPeriodEnd: item.current_period_end,
|
||||||
|
tiers: item.price.tiers
|
||||||
|
? JSON.stringify(item.price.tiers)
|
||||||
|
: null,
|
||||||
|
interval: item.plan.interval
|
||||||
|
};
|
||||||
|
});
|
||||||
if (itemsToUpsert.length > 0) {
|
if (itemsToUpsert.length > 0) {
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
await trx
|
await trx
|
||||||
|
|||||||
@@ -453,8 +453,19 @@ export default function BillingPage() {
|
|||||||
// Calculate current usage cost for display
|
// Calculate current usage cost for display
|
||||||
const getUserCount = () => getUsageValue(USERS);
|
const getUserCount = () => getUsageValue(USERS);
|
||||||
const getPricePerUser = () => {
|
const getPricePerUser = () => {
|
||||||
if (currentTier === "tier2") return 5;
|
console.log("Calculating price per user, tierSubscription:", tierSubscription);
|
||||||
if (currentTier === "tier3") return 10;
|
if (!tierSubscription?.items) return 0;
|
||||||
|
|
||||||
|
// Find the subscription item for USERS feature
|
||||||
|
const usersItem = tierSubscription.items.find(
|
||||||
|
(item) => item.planId === USERS
|
||||||
|
);
|
||||||
|
|
||||||
|
// unitAmount is in cents, convert to dollars
|
||||||
|
if (usersItem?.unitAmount) {
|
||||||
|
return usersItem.unitAmount / 100;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user