added product group functionality
This commit is contained in:
@@ -58,6 +58,7 @@ export const handleCheckoutSessionCompleted = async ({
|
||||
await CusProductService.expireCurrentProduct({
|
||||
sb,
|
||||
internalCustomerId: customer.internal_id,
|
||||
productGroup: product.group,
|
||||
});
|
||||
|
||||
console.log("Creating full customer product");
|
||||
|
||||
@@ -43,6 +43,7 @@ export const handleSubscriptionDeleted = async ({
|
||||
const futureProduct = await CusProductService.activateFutureProduct({
|
||||
sb,
|
||||
internalCustomerId: cusProduct.internal_customer_id,
|
||||
productGroup: cusProduct.product.group,
|
||||
});
|
||||
|
||||
if (futureProduct) {
|
||||
|
||||
@@ -105,17 +105,18 @@ const handleExistingProduct = async ({
|
||||
product: FullProduct;
|
||||
}) => {
|
||||
const { sb } = req;
|
||||
const existingCusProduct = await CusProductService.getCurrentProduct({
|
||||
const currentProduct = await CusProductService.getCurrentProductByGroup({
|
||||
sb,
|
||||
internalCustomerId: customer.internal_id,
|
||||
productGroup: product.group,
|
||||
});
|
||||
|
||||
// 2. Don't allow customer to get multiple of the same product
|
||||
if (existingCusProduct?.internal_product_id === product.internal_id) {
|
||||
if (currentProduct?.product.internal_id === product.internal_id) {
|
||||
// If there's a future product, delete, else
|
||||
const deletedCusProduct = await CusProductService.deleteFutureProduct({
|
||||
sb,
|
||||
internalCustomerId: customer.internal_id,
|
||||
productGroup: product.group,
|
||||
});
|
||||
|
||||
if (deletedCusProduct) {
|
||||
@@ -126,9 +127,9 @@ const handleExistingProduct = async ({
|
||||
);
|
||||
}
|
||||
// Continue current product subscription
|
||||
if (existingCusProduct.processor.subscription_id) {
|
||||
if (currentProduct.processor.subscription_id) {
|
||||
await stripeCli.subscriptions.update(
|
||||
existingCusProduct.processor.subscription_id,
|
||||
currentProduct.processor.subscription_id,
|
||||
{
|
||||
cancel_at_period_end: false,
|
||||
}
|
||||
@@ -146,7 +147,7 @@ const handleExistingProduct = async ({
|
||||
return true;
|
||||
} else {
|
||||
throw new RecaseError({
|
||||
message: `Customer ${customer.id} already has product ${existingCusProduct.product_id}`,
|
||||
message: `Customer ${customer.id} already has product ${currentProduct.product_id}`,
|
||||
code: ErrCode.CustomerAlreadyHasProduct,
|
||||
statusCode: 400,
|
||||
});
|
||||
@@ -154,7 +155,7 @@ const handleExistingProduct = async ({
|
||||
}
|
||||
|
||||
// 3. If no existing product, check if new product is add-on
|
||||
if (!existingCusProduct && product.is_add_on) {
|
||||
if (!currentProduct && product.is_add_on) {
|
||||
throw new RecaseError({
|
||||
message: `Customer has no base product`,
|
||||
code: ErrCode.CustomerHasNoBaseProduct,
|
||||
@@ -162,7 +163,7 @@ const handleExistingProduct = async ({
|
||||
});
|
||||
}
|
||||
|
||||
return existingCusProduct;
|
||||
return currentProduct;
|
||||
};
|
||||
|
||||
const checkStripeConnections = async ({
|
||||
|
||||
@@ -198,21 +198,25 @@ export const expireOrDeleteCusProduct = async ({
|
||||
sb,
|
||||
customer,
|
||||
startsAt,
|
||||
productGroup,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
customer: Customer;
|
||||
startsAt?: number;
|
||||
productGroup: string;
|
||||
}) => {
|
||||
// 1. If startsAt
|
||||
if (startsAt && startsAt > Date.now()) {
|
||||
await CusProductService.deleteFutureProduct({
|
||||
sb,
|
||||
internalCustomerId: customer.internal_id,
|
||||
productGroup,
|
||||
});
|
||||
} else {
|
||||
await CusProductService.expireCurrentProduct({
|
||||
sb,
|
||||
internalCustomerId: customer.internal_id,
|
||||
productGroup,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -247,6 +251,7 @@ export const createFullCusProduct = async ({
|
||||
sb,
|
||||
customer,
|
||||
startsAt,
|
||||
productGroup: product.group,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { createFullCusProduct } from "../add-product/createFullCusProduct.js";
|
||||
import { getCusPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { InvoiceService } from "../invoices/InvoiceService.js";
|
||||
import { handleAddProduct } from "../add-product/handleAddProduct.js";
|
||||
import { CusProductService } from "../products/CusProductService.js";
|
||||
|
||||
const scheduleStripeSubscription = async ({
|
||||
customer,
|
||||
@@ -123,7 +124,17 @@ const handleDowngrade = async ({
|
||||
});
|
||||
|
||||
for (const schedule of schedules.data) {
|
||||
if (schedule.status !== "canceled") {
|
||||
const existingCusProduct = await CusProductService.getByScheduleId({
|
||||
sb: req.sb,
|
||||
scheduleId: schedule.id,
|
||||
});
|
||||
|
||||
// Delete only if not in the same group
|
||||
if (
|
||||
(!existingCusProduct ||
|
||||
existingCusProduct.product.group === product.group) &&
|
||||
schedule.status !== "canceled"
|
||||
) {
|
||||
await stripeCli.subscriptionSchedules.cancel(schedule.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,38 +65,33 @@ export class CusProductService {
|
||||
return data;
|
||||
}
|
||||
|
||||
static async getCurrentProduct({
|
||||
static async getCurrentProductByGroup({
|
||||
sb,
|
||||
internalCustomerId,
|
||||
productGroup,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
internalCustomerId: string;
|
||||
productGroup: string;
|
||||
}) {
|
||||
const { data, error } = await sb
|
||||
.from("customer_products")
|
||||
.select("*, product:products!inner(*)")
|
||||
.eq("internal_customer_id", internalCustomerId)
|
||||
.eq("product.group", productGroup)
|
||||
.eq("product.is_add_on", false)
|
||||
.neq("status", CusProductStatus.Expired)
|
||||
.neq("status", CusProductStatus.Scheduled);
|
||||
.neq("status", CusProductStatus.Scheduled)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
if (error.code === "PGRST116") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (data.length > 1) {
|
||||
throw new RecaseError({
|
||||
message: "Multiple products found for customer",
|
||||
code: ErrCode.MultipleProductsFound,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data[0];
|
||||
return data;
|
||||
}
|
||||
|
||||
static async getByCusAndProductId({
|
||||
@@ -185,6 +180,29 @@ export class CusProductService {
|
||||
return data;
|
||||
}
|
||||
|
||||
static async getByScheduleId({
|
||||
sb,
|
||||
scheduleId,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
scheduleId: string;
|
||||
}) {
|
||||
const { data, error } = await sb
|
||||
.from("customer_products")
|
||||
.select("*, product:products!inner(*)")
|
||||
.eq("processor->>subscription_schedule_id", scheduleId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
if (error.code === "PGRST116") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static async updateStatusByStripeSubId({
|
||||
sb,
|
||||
stripeSubId,
|
||||
@@ -242,13 +260,17 @@ export class CusProductService {
|
||||
static async expireCurrentProduct({
|
||||
sb,
|
||||
internalCustomerId,
|
||||
productGroup,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
internalCustomerId: string;
|
||||
productGroup: string;
|
||||
}) {
|
||||
const currentProduct = await this.getCurrentProduct({
|
||||
// TO WORK ON EXPIRING
|
||||
const currentProduct = await this.getCurrentProductByGroup({
|
||||
sb,
|
||||
internalCustomerId,
|
||||
productGroup,
|
||||
});
|
||||
|
||||
if (!currentProduct) {
|
||||
@@ -271,9 +293,11 @@ export class CusProductService {
|
||||
static async activateFutureProduct({
|
||||
sb,
|
||||
internalCustomerId,
|
||||
productGroup,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
internalCustomerId: string;
|
||||
productGroup: string;
|
||||
}) {
|
||||
const { data, error } = await sb
|
||||
.from("customer_products")
|
||||
@@ -281,6 +305,7 @@ export class CusProductService {
|
||||
status: CusProductStatus.Active,
|
||||
})
|
||||
.eq("internal_customer_id", internalCustomerId)
|
||||
.eq("product.group", productGroup)
|
||||
.eq("status", CusProductStatus.Scheduled)
|
||||
.select()
|
||||
.single();
|
||||
@@ -300,25 +325,39 @@ export class CusProductService {
|
||||
static async deleteFutureProduct({
|
||||
sb,
|
||||
internalCustomerId,
|
||||
productGroup,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
internalCustomerId: string;
|
||||
productGroup: string;
|
||||
}) {
|
||||
// 1. Get all products in same group
|
||||
const { data, error } = await sb
|
||||
.from("customer_products")
|
||||
.delete()
|
||||
.select("*, product:products!inner(*)")
|
||||
.eq("internal_customer_id", internalCustomerId)
|
||||
.eq("status", CusProductStatus.Scheduled)
|
||||
.eq("product.group", productGroup)
|
||||
.eq("status", CusProductStatus.Scheduled);
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Delete product
|
||||
const { error: deleteError } = await sb
|
||||
.from("customer_products")
|
||||
.delete()
|
||||
.eq("id", data[0].id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
if (error.code === "PGRST116") {
|
||||
if (deleteError) {
|
||||
if (deleteError.code === "PGRST116") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
throw deleteError;
|
||||
}
|
||||
|
||||
return data;
|
||||
return data[0];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user