feat: add stripe webhooks

This commit is contained in:
Mauricio Siu
2024-10-20 15:08:44 -06:00
parent fe0a662afd
commit ffe7b04bea
9 changed files with 4476 additions and 214 deletions

View File

@@ -37,16 +37,23 @@ export const ReviewPayment = ({ isAnnual, serverQuantity }: Props) => {
},
);
// const { data: calculateNewMonthlyCost } =
// api.stripe.calculateNewMonthlyCost.useQuery(
// {
// serverQuantity,
// isAnnual,
// },
// {
// enabled: !!serverQuantity && isOpen,
// },
// );
const { data: calculateNewMonthlyCost } =
api.stripe.calculateNewMonthlyCost.useQuery(
{
serverQuantity,
isAnnual,
},
{
enabled: !!serverQuantity && isOpen,
},
);
const isSameServersQty =
Number(billingSubscription?.totalServers) === serverQuantity;
const isSameCost =
Number(calculateNewMonthlyCost) ===
Number(billingSubscription?.monthlyAmount);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
@@ -86,7 +93,6 @@ export const ReviewPayment = ({ isAnnual, serverQuantity }: Props) => {
{billingSubscription?.nextPaymentDate
? format(billingSubscription?.nextPaymentDate, "MMM d, yyyy")
: "-"}
{/* {format(billingSubscription?.nextPaymentDate, "MMM d, yyyy")} */}
</span>
</div>
</div>
@@ -112,19 +118,15 @@ export const ReviewPayment = ({ isAnnual, serverQuantity }: Props) => {
<div className="grid flex-1 gap-2">
<Label>Difference</Label>
<span className="text-sm text-muted-foreground">
{Number(billingSubscription?.totalServers) === serverQuantity
? "-"
: `$${calculateUpgradeCost} USD`}{" "}
{isSameServersQty ? "-" : `$${calculateUpgradeCost} USD`}{" "}
</span>
</div>
{/* <div className="grid flex-1 gap-2">
<div className="grid flex-1 gap-2">
<Label>New {isAnnual ? "annual" : "monthly"} cost</Label>
<span className="text-sm text-muted-foreground">
{Number(billingSubscription?.totalServers) === serverQuantity
? "-"
: `${calculateNewMonthlyCost} USD`}{" "}
{isSameCost ? "-" : `$${calculateNewMonthlyCost} USD`}{" "}
</span>
</div> */}
</div>
</div>
</div>

View File

@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { NumberInput } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -6,6 +7,7 @@ import { api } from "@/utils/api";
import { loadStripe } from "@stripe/stripe-js";
import clsx from "clsx";
import { CheckIcon, MinusIcon, PlusIcon } from "lucide-react";
import { useRouter } from "next/router";
import React, { useState } from "react";
import { toast } from "sonner";
import { ReviewPayment } from "./review-payment";
@@ -25,35 +27,88 @@ export const calculatePrice = (count: number, isAnnual = false) => {
return 7.99 + (count - 3) * 3.5;
};
export const calculateYearlyCost = (serverQuantity: number) => {
const count = serverQuantity;
if (count === 1) return 4.0 * 12;
if (count <= 3) return 7.99 * 12;
return (7.99 + (count - 3) * 3.5) * 12;
};
export const ShowBilling = () => {
const router = useRouter();
const { data: billingSubscription } =
api.stripe.getBillingSubscription.useQuery(undefined);
const { data: servers } = api.server.all.useQuery(undefined);
const { data: admin } = api.admin.one.useQuery();
const { data, refetch } = api.stripe.getProducts.useQuery();
const { mutateAsync: createCheckoutSession } =
api.stripe.createCheckoutSession.useMutation();
const { mutateAsync: createCustomerPortalSession } =
api.stripe.createCustomerPortalSession.useMutation();
const [serverQuantity, setServerQuantity] = useState(3);
const { mutateAsync: upgradeSubscription } =
api.stripe.upgradeSubscription.useMutation();
const { mutateAsync: upgradeSubscriptionMonthly } =
api.stripe.upgradeSubscriptionMonthly.useMutation();
const { mutateAsync: upgradeSubscriptionAnnual } =
api.stripe.upgradeSubscriptionAnnual.useMutation();
const [isAnnual, setIsAnnual] = useState(false);
// useEffect(() => {
// if (billingSubscription) {
// setIsAnnual(
// (prevIsAnnual) =>
// billingSubscription.billingInterval === "year" &&
// prevIsAnnual !== true,
// );
// }
// }, [billingSubscription]);
const handleCheckout = async (productId: string) => {
const stripe = await stripePromise;
if (data && admin?.stripeSubscriptionId && data.subscriptions.length > 0) {
upgradeSubscription({
subscriptionId: admin?.stripeSubscriptionId,
serverQuantity,
isAnnual,
})
.then(async (subscription) => {
toast.success("Subscription upgraded successfully");
await refetch();
if (isAnnual) {
upgradeSubscriptionAnnual({
subscriptionId: admin?.stripeSubscriptionId,
serverQuantity,
})
.catch((error) => {
toast.error("Error to upgrade the subscription");
console.error(error);
});
.then(async (subscription) => {
if (subscription.type === "new") {
await stripe?.redirectToCheckout({
sessionId: subscription.sessionId,
});
return;
}
toast.success("Subscription upgraded successfully");
await refetch();
})
.catch((error) => {
toast.error("Error to upgrade the subscription");
console.error(error);
});
} else {
upgradeSubscriptionMonthly({
subscriptionId: admin?.stripeSubscriptionId,
serverQuantity,
})
.then(async (subscription) => {
if (subscription.type === "new") {
await stripe?.redirectToCheckout({
sessionId: subscription.sessionId,
});
return;
}
toast.success("Subscription upgraded successfully");
await refetch();
})
.catch((error) => {
toast.error("Error to upgrade the subscription");
console.error(error);
});
}
} else {
createCheckoutSession({
productId,
@@ -66,25 +121,31 @@ export const ShowBilling = () => {
});
}
};
const products = data?.products.filter((product) => {
const interval = product?.default_price?.recurring?.interval;
return isAnnual ? interval === "year" : interval === "month";
});
return (
<div className="flex flex-col gap-4 w-full justify-center">
<Badge>{admin?.stripeSubscriptionStatus}</Badge>
<Tabs
defaultValue="monthly"
value={isAnnual ? "annual" : "monthly"}
className="w-full"
onValueChange={(e) => {
console.log(e);
setIsAnnual(e === "annual");
}}
onValueChange={(e) => setIsAnnual(e === "annual")}
>
<TabsList>
<TabsTrigger value="monthly">Monthly</TabsTrigger>
<TabsTrigger value="annual">Annual</TabsTrigger>
</TabsList>
</Tabs>
{data?.products?.map((product) => {
const featured = true;
{products?.map((product) => {
// const suscripcion = data?.subscriptions.find((subscription) =>
// subscription.items.data.find((item) => item.pr === product.id),
// );
const featured = true;
return (
<div key={product.id}>
<section
@@ -95,6 +156,23 @@ export const ShowBilling = () => {
: "lg:py-8",
)}
>
{isAnnual ? (
<div className="flex flex-row gap-2 items-center">
<p className=" text-2xl font-semibold tracking-tight text-primary ">
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
</p>
|
<p className=" text-base font-semibold tracking-tight text-muted-foreground">
${" "}
{(calculatePrice(serverQuantity, isAnnual) / 12).toFixed(2)}{" "}
/ Month USD
</p>
</div>
) : (
<p className=" text-2xl font-semibold tracking-tight text-primary ">
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
</p>
)}
<h3 className="mt-5 font-medium text-lg text-white">
{product.name}
</h3>
@@ -106,9 +184,6 @@ export const ShowBilling = () => {
>
{product.description}
</p>
<p className="order-first text-3xl font-semibold tracking-tight text-primary">
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
</p>
<ul
role="list"
@@ -123,7 +198,8 @@ export const ShowBilling = () => {
"Self-hosted on your own infrastructure",
"Full access to all deployment features",
"Dokploy integration",
"Free",
"Backups",
"All Incoming features",
].map((feature) => (
<li key={feature} className="flex text-muted-foreground">
<CheckIcon />
@@ -181,21 +257,34 @@ export const ShowBilling = () => {
</div>
<div
className={cn(
data.subscriptions.length > 0
data?.subscriptions && data?.subscriptions?.length > 0
? "justify-between"
: "justify-end",
"flex flex-row items-center gap-2 mt-4",
)}
>
{data.subscriptions.length > 0 && (
<ReviewPayment
isAnnual={isAnnual}
serverQuantity={serverQuantity}
/>
)}
{data &&
data?.subscriptions?.length > 0 &&
billingSubscription?.billingInterval === "year" &&
isAnnual && (
<ReviewPayment
isAnnual={true}
serverQuantity={serverQuantity}
/>
)}
{data &&
data?.subscriptions?.length > 0 &&
billingSubscription?.billingInterval === "month" &&
!isAnnual && (
<ReviewPayment
isAnnual={false}
serverQuantity={serverQuantity}
/>
)}
<div className="justify-end">
<div className="justify-end w-full">
<Button
className="w-full"
onClick={async () => {
handleCheckout(product.id);
}}
@@ -211,18 +300,21 @@ export const ShowBilling = () => {
);
})}
{/* <Button
variant="destructive"
onClick={async () => {
// Crear una sesión del portal del cliente
const session = await createCustomerPortalSession();
<Button
variant="secondary"
onClick={async () => {
// Crear una sesión del portal del cliente
const session = await createCustomerPortalSession();
// Redirigir al portal del cliente en Stripe
window.location.href = session.url;
}}
>
Manage Subscription
</Button> */}
// router.push(session.url,"",{});
window.open(session.url);
// Redirigir al portal del cliente en Stripe
// window.location.href = session.url;
}}
>
Manage Subscription
</Button>
</div>
);
};

View File

@@ -0,0 +1 @@
ALTER TABLE "admin" ADD COLUMN "stripeSubscriptionStatus" text;

File diff suppressed because it is too large Load Diff

View File

@@ -295,6 +295,13 @@
"when": 1729314952330,
"tag": "0041_small_aaron_stack",
"breakpoints": true
},
{
"idx": 42,
"version": "6",
"when": 1729455812207,
"tag": "0042_smooth_swordsman",
"breakpoints": true
}
]
}

View File

@@ -0,0 +1,129 @@
import { db } from "@/server/db";
import { admins, github } from "@/server/db/schema";
import { eq } from "drizzle-orm";
import { buffer } from "node:stream/consumers";
import type { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", {
apiVersion: "2024-09-30.acacia",
});
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET || "";
export const config = {
api: {
bodyParser: false, // Deshabilitar el body parser de Next.js
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const buf = await buffer(req); // Leer el raw body como un Buffer
const sig = req.headers["stripe-signature"] as string;
let event: Stripe.Event;
try {
// Verificar el evento usando el raw body (buf)
event = stripe.webhooks.constructEvent(buf, sig, endpointSecret);
const newSubscription = event.data.object as Stripe.Subscription;
console.log(event.type);
switch (event.type) {
case "customer.subscription.created":
await db
.update(admins)
.set({
stripeSubscriptionId: newSubscription.id,
stripeSubscriptionStatus: newSubscription.status,
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
)
.returning();
break;
case "customer.subscription.deleted":
await db
.update(admins)
.set({
stripeSubscriptionStatus: "canceled",
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
case "customer.subscription.updated":
console.log(newSubscription.status);
// Suscripción actualizada (upgrade, downgrade, cambios)
await db
.update(admins)
.set({
stripeSubscriptionStatus: newSubscription.status,
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
case "invoice.payment_succeeded":
console.log(newSubscription.customer);
await db
.update(admins)
.set({
stripeSubscriptionStatus: "active",
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
case "invoice.payment_failed":
// Pago fallido
await db
.update(admins)
.set({
stripeSubscriptionStatus: "payment_failed",
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).json({ received: true });
} catch (err) {
console.error("Webhook signature verification failed.", err.message);
return res.status(400).send("Webhook Error: ");
}
}

View File

@@ -1,7 +1,10 @@
import { admins } from "@/server/db/schema";
import {
ADDITIONAL_PRICE_YEARLY_ID,
BASE_PRICE_MONTHLY_ID,
BASE_PRICE_YEARLY_ID,
GROWTH_PRICE_MONTHLY_ID,
GROWTH_PRICE_YEARLY_ID,
SERVER_ADDITIONAL_PRICE_MONTHLY_ID,
getStripeItems,
getStripePrices,
@@ -66,12 +69,14 @@ export const stripeRouter = createTRPCRouter({
const session = await stripe.checkout.sessions.create({
// payment_method_types: ["card"],
mode: "subscription",
line_items: [...items],
line_items: items,
// subscription_data: {
// trial_period_days: 0,
// },
metadata: {
serverQuantity: input.serverQuantity,
subscription_data: {
metadata: {
serverQuantity: input.serverQuantity,
},
},
success_url:
"http://localhost:3000/api/stripe.success?sessionId={CHECKOUT_SESSION_ID}",
@@ -81,12 +86,11 @@ export const stripeRouter = createTRPCRouter({
return { sessionId: session.id };
}),
upgradeSubscription: adminProcedure
upgradeSubscriptionMonthly: adminProcedure
.input(
z.object({
subscriptionId: z.string(), // ID de la suscripción actual
subscriptionId: z.string(),
serverQuantity: z.number().min(1),
isAnnual: z.boolean(),
}),
)
.mutation(async ({ ctx, input }) => {
@@ -94,32 +98,53 @@ export const stripeRouter = createTRPCRouter({
apiVersion: "2024-09-30.acacia",
});
const { subscriptionId, serverQuantity, isAnnual } = input;
const { subscriptionId, serverQuantity } = input;
const admin = await findAdminById(ctx.user.adminId);
const suscription = await stripe.subscriptions.retrieve(subscriptionId);
const currentItems = suscription.items.data;
// If have a monthly plan, we need to create a new subscription
const haveMonthlyPlan = currentItems.find(
(item) =>
item.price.id === BASE_PRICE_YEARLY_ID ||
item.price.id === GROWTH_PRICE_YEARLY_ID ||
item.price.id === ADDITIONAL_PRICE_YEARLY_ID,
);
// Price IDs
// const price1ServerId = "price_1QBk3bF3cxQuHeOzCmSlyFB3"; // $4.00
// const priceUpToThreeId = "price_1QBkPiF3cxQuHeOzceNiM2OJ"; // $7.99
// const priceAdditionalId = "price_1QBkr9F3cxQuHeOzTBo46Bmy"; // $3.50
if (haveMonthlyPlan) {
const items = getStripeItems(serverQuantity, false);
const session = await stripe.checkout.sessions.create({
line_items: items,
mode: "subscription",
...(admin.stripeCustomerId && {
customer: admin.stripeCustomerId,
}),
subscription_data: {
metadata: {
serverQuantity: input.serverQuantity,
},
},
success_url:
"http://localhost:3000/api/stripe.success?sessionId={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/dashboard/settings/billing",
});
return {
type: "new",
success: true,
sessionId: session.id,
};
}
const basePriceId = BASE_PRICE_MONTHLY_ID;
const growthPriceId = GROWTH_PRICE_MONTHLY_ID;
const additionalPriceId = SERVER_ADDITIONAL_PRICE_MONTHLY_ID;
// Obtener suscripción actual
const { baseItem, additionalItem } = await getStripeSubscriptionItems(
subscriptionId,
isAnnual,
false,
);
// const updateBasePlan = async (newPriceId: string) => {
// await stripe.subscriptions.update(subscriptionId, {
// items: [
// {
// id: baseItem?.id,
// price: newPriceId,
// quantity: 1,
// },
// ],
// proration_behavior: "always_invoice",
// });
// };
const deleteAdditionalItem = async () => {
if (additionalItem) {
await stripe.subscriptionItems.del(additionalItem.id);
@@ -137,7 +162,7 @@ export const stripeRouter = createTRPCRouter({
await stripe.subscriptions.update(subscriptionId, {
items: [
{
price: SERVER_ADDITIONAL_PRICE_MONTHLY_ID,
price: additionalPriceId,
quantity: additionalServers,
},
],
@@ -148,38 +173,137 @@ export const stripeRouter = createTRPCRouter({
if (serverQuantity === 1) {
await deleteAdditionalItem();
if (
baseItem?.price.id !== BASE_PRICE_MONTHLY_ID &&
baseItem?.price.id
) {
await updateBasePlan(
subscriptionId,
baseItem?.id,
BASE_PRICE_MONTHLY_ID,
);
if (baseItem?.price.id !== basePriceId && baseItem?.price.id) {
await updateBasePlan(subscriptionId, baseItem?.id, basePriceId);
}
} else if (serverQuantity >= 2 && serverQuantity <= 3) {
await deleteAdditionalItem();
if (
baseItem?.price.id !== GROWTH_PRICE_MONTHLY_ID &&
baseItem?.price.id
) {
await updateBasePlan(
subscriptionId,
baseItem?.id,
GROWTH_PRICE_MONTHLY_ID,
);
if (baseItem?.price.id !== growthPriceId && baseItem?.price.id) {
await updateBasePlan(subscriptionId, baseItem?.id, growthPriceId);
}
} else if (serverQuantity > 3) {
if (
baseItem?.price.id !== GROWTH_PRICE_MONTHLY_ID &&
baseItem?.price.id
) {
await updateBasePlan(
subscriptionId,
baseItem?.id,
GROWTH_PRICE_MONTHLY_ID,
);
if (baseItem?.price.id !== growthPriceId && baseItem?.price.id) {
await updateBasePlan(subscriptionId, baseItem?.id, growthPriceId);
}
const additionalServers = serverQuantity - 3;
await updateOrCreateAdditionalItem(additionalServers);
}
await stripe.subscriptions.update(subscriptionId, {
metadata: {
serverQuantity: serverQuantity.toString(),
},
});
return { success: true };
}),
upgradeSubscriptionAnnual: adminProcedure
.input(
z.object({
subscriptionId: z.string(),
serverQuantity: z.number().min(1),
}),
)
.mutation(async ({ ctx, input }) => {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", {
apiVersion: "2024-09-30.acacia",
});
const { subscriptionId, serverQuantity } = input;
const currentSubscription =
await stripe.subscriptions.retrieve(subscriptionId);
if (!currentSubscription) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Subscription not found",
});
}
const admin = await findAdminById(ctx.user.adminId);
const currentItems = currentSubscription.items.data;
// If have a monthly plan, we need to create a new subscription
const haveMonthlyPlan = currentItems.find(
(item) =>
item.price.id === BASE_PRICE_MONTHLY_ID ||
item.price.id === GROWTH_PRICE_MONTHLY_ID ||
item.price.id === SERVER_ADDITIONAL_PRICE_MONTHLY_ID,
);
if (haveMonthlyPlan) {
const items = getStripeItems(serverQuantity, true);
const session = await stripe.checkout.sessions.create({
line_items: items,
mode: "subscription",
...(admin.stripeCustomerId && {
customer: admin.stripeCustomerId,
}),
subscription_data: {
metadata: {
serverQuantity: input.serverQuantity,
},
},
success_url:
"http://localhost:3000/api/stripe.success?sessionId={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/dashboard/settings/billing",
});
return {
type: "new",
success: true,
sessionId: session.id,
};
}
const basePriceId = BASE_PRICE_YEARLY_ID;
const growthPriceId = GROWTH_PRICE_YEARLY_ID;
const additionalPriceId = ADDITIONAL_PRICE_YEARLY_ID;
// Obtener suscripción actual
const { baseItem, additionalItem } = await getStripeSubscriptionItems(
subscriptionId,
true,
);
const deleteAdditionalItem = async () => {
if (additionalItem) {
await stripe.subscriptionItems.del(additionalItem.id);
}
};
const updateOrCreateAdditionalItem = async (
additionalServers: number,
) => {
if (additionalItem) {
await stripe.subscriptionItems.update(additionalItem.id, {
quantity: additionalServers,
});
} else {
await stripe.subscriptions.update(subscriptionId, {
items: [
{
price: additionalPriceId,
quantity: additionalServers,
},
],
proration_behavior: "always_invoice",
});
}
};
if (serverQuantity === 1) {
await deleteAdditionalItem();
if (baseItem?.price.id !== basePriceId && baseItem?.price.id) {
await updateBasePlan(subscriptionId, baseItem?.id, basePriceId);
}
} else if (serverQuantity >= 2 && serverQuantity <= 3) {
await deleteAdditionalItem();
if (baseItem?.price.id !== growthPriceId && baseItem?.price.id) {
await updateBasePlan(subscriptionId, baseItem?.id, growthPriceId);
}
} else if (serverQuantity > 3) {
if (baseItem?.price.id !== growthPriceId && baseItem?.price.id) {
await updateBasePlan(subscriptionId, baseItem?.id, growthPriceId);
}
const additionalServers = serverQuantity - 3;
await updateOrCreateAdditionalItem(additionalServers);
@@ -231,6 +355,18 @@ export const stripeRouter = createTRPCRouter({
const session = await stripe.checkout.sessions.retrieve(sessionId);
if (session.payment_status === "paid") {
const admin = await findAdminById(ctx.user.adminId);
if (admin.stripeSubscriptionId) {
const subscription = await stripe.subscriptions.retrieve(
admin.stripeSubscriptionId,
);
if (subscription.status === "active") {
await stripe.subscriptions.update(admin.stripeSubscriptionId, {
cancel_at_period_end: true,
});
}
}
console.log("Payment successful!");
const stripeCustomerId = session.customer as string;
@@ -267,8 +403,9 @@ export const stripeRouter = createTRPCRouter({
const subscription =
await stripe.subscriptions.retrieve(stripeSubscriptionId);
let billingInterval: Stripe.Price.Recurring.Interval | undefined;
const totalServers = subscription.metadata.serverQuantity;
console.log(subscription.metadata);
let totalAmount = 0;
for (const item of subscription.items.data) {
@@ -276,12 +413,14 @@ export const stripeRouter = createTRPCRouter({
const amountPerUnit = item.price.unit_amount / 100;
totalAmount += quantity * amountPerUnit;
billingInterval = item.price.recurring?.interval;
}
return {
nextPaymentDate: new Date(subscription.current_period_end * 1000),
monthlyAmount: `${totalAmount.toFixed(2)} USD`,
monthlyAmount: totalAmount.toFixed(2),
totalServers,
billingInterval,
};
}),
@@ -306,20 +445,19 @@ export const stripeRouter = createTRPCRouter({
}
const subscriptionId = admin.stripeSubscriptionId;
const items = await getStripeSubscriptionItemsCalculate(
subscriptionId,
input.serverQuantity,
input.isAnnual,
);
console.log(items);
if (!subscriptionId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Subscription not found",
});
}
const items = await getStripeSubscriptionItemsCalculate(
subscriptionId,
input.serverQuantity,
input.isAnnual,
);
const upcomingInvoice = await stripe.invoices.retrieveUpcoming({
subscription: subscriptionId,
subscription_items: items,

View File

@@ -78,99 +78,57 @@ export const getStripeSubscriptionItemsCalculate = async (
const currentItems = subscription.items.data;
const items = [];
if (isAnnual) {
const baseItem = currentItems.find(
(item) =>
item.price.id === BASE_PRICE_YEARLY_ID ||
item.price.id === GROWTH_PRICE_YEARLY_ID,
);
const additionalItem = currentItems.find(
(item) => item.price.id === ADDITIONAL_PRICE_YEARLY_ID,
);
if (serverQuantity === 1) {
if (baseItem) {
items.push({
id: baseItem.id,
price: BASE_PRICE_YEARLY_ID,
quantity: 1,
});
}
} else if (serverQuantity <= 3) {
if (baseItem) {
items.push({
id: baseItem.id,
price: GROWTH_PRICE_YEARLY_ID,
quantity: 1,
});
}
} else {
if (baseItem) {
items.push({
id: baseItem.id,
price: GROWTH_PRICE_YEARLY_ID,
quantity: 1,
});
}
const basePriceId = isAnnual ? BASE_PRICE_YEARLY_ID : BASE_PRICE_MONTHLY_ID;
const growthPriceId = isAnnual
? GROWTH_PRICE_YEARLY_ID
: GROWTH_PRICE_MONTHLY_ID;
const additionalPriceId = isAnnual
? ADDITIONAL_PRICE_YEARLY_ID
: SERVER_ADDITIONAL_PRICE_MONTHLY_ID;
if (additionalItem) {
items.push({
id: additionalItem.id,
price: ADDITIONAL_PRICE_YEARLY_ID,
quantity: serverQuantity - 3,
});
} else {
items.push({
price: ADDITIONAL_PRICE_YEARLY_ID,
quantity: serverQuantity - 3,
});
}
const baseItem = currentItems.find(
(item) => item.price.id === basePriceId || item.price.id === growthPriceId,
);
const additionalItem = currentItems.find(
(item) => item.price.id === additionalPriceId,
);
if (serverQuantity === 1) {
if (baseItem) {
items.push({
id: baseItem.id,
price: basePriceId,
quantity: 1,
});
}
} else if (serverQuantity <= 3) {
if (baseItem) {
items.push({
id: baseItem.id,
price: growthPriceId,
quantity: 1,
});
}
} else {
const baseItem = currentItems.find(
(item) =>
item.price.id === BASE_PRICE_MONTHLY_ID ||
item.price.id === GROWTH_PRICE_MONTHLY_ID,
);
const additionalItem = currentItems.find(
(item) => item.price.id === SERVER_ADDITIONAL_PRICE_MONTHLY_ID,
);
if (serverQuantity === 1) {
if (baseItem) {
items.push({
id: baseItem.id,
price: BASE_PRICE_MONTHLY_ID,
quantity: 1,
});
}
} else if (serverQuantity <= 3) {
if (baseItem) {
items.push({
id: baseItem.id,
price: GROWTH_PRICE_MONTHLY_ID,
quantity: 1,
});
}
} else {
if (baseItem) {
items.push({
id: baseItem.id,
price: GROWTH_PRICE_MONTHLY_ID,
quantity: 1,
});
}
if (baseItem) {
items.push({
id: baseItem.id,
price: growthPriceId,
quantity: 1,
});
}
if (additionalItem) {
items.push({
id: additionalItem.id,
price: SERVER_ADDITIONAL_PRICE_MONTHLY_ID,
quantity: serverQuantity - 3,
});
} else {
items.push({
price: SERVER_ADDITIONAL_PRICE_MONTHLY_ID,
quantity: serverQuantity - 3,
});
}
if (additionalItem) {
items.push({
id: additionalItem.id,
price: additionalPriceId,
quantity: serverQuantity - 3,
});
} else {
items.push({
price: additionalPriceId,
quantity: serverQuantity - 3,
});
}
}

View File

@@ -30,6 +30,7 @@ export const admins = pgTable("admin", {
.$defaultFn(() => new Date().toISOString()),
stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"),
stripeSubscriptionStatus: text("stripeSubscriptionStatus"),
totalServers: integer("totalServers").notNull().default(0),
});