fix: returning trial available in getProductCheckPreview
This commit is contained in:
@@ -28,9 +28,9 @@ componentRouter.get("/pricing_table", async (req: any, res) =>
|
||||
ProductService.listFull({ db, orgId, env }),
|
||||
(async () => {
|
||||
if (!customerId) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
return await CusService.get({
|
||||
return await CusService.getFull({
|
||||
db,
|
||||
orgId,
|
||||
env,
|
||||
@@ -59,48 +59,66 @@ componentRouter.get("/pricing_table", async (req: any, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
let cusProducts: FullCusProduct[] | null = null;
|
||||
let batchResponse = [];
|
||||
for (let p of products) {
|
||||
let prod = getProductResponse({ product: p, features });
|
||||
let curMainProduct, curScheduledProduct;
|
||||
|
||||
if (customer) {
|
||||
cusProducts = await CusProductService.list({
|
||||
db,
|
||||
internalCustomerId: customer.internal_id,
|
||||
inStatuses: [
|
||||
CusProductStatus.Active,
|
||||
CusProductStatus.PastDue,
|
||||
CusProductStatus.Scheduled,
|
||||
],
|
||||
});
|
||||
if (customer) {
|
||||
let res = getExistingCusProducts({
|
||||
product: p,
|
||||
cusProducts: customer.customer_products,
|
||||
});
|
||||
|
||||
curMainProduct = res.curMainProduct;
|
||||
curScheduledProduct = res.curScheduledProduct;
|
||||
}
|
||||
|
||||
batchResponse.push(
|
||||
toPricecnProduct({
|
||||
db,
|
||||
org,
|
||||
product: prod as ProductV2,
|
||||
fullProduct: p,
|
||||
features,
|
||||
curMainProduct,
|
||||
curScheduledProduct,
|
||||
otherProducts: products.filter((other) => other.id != p.id),
|
||||
fullCus: customer,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let pricecnProds = await Promise.all(
|
||||
products
|
||||
// .filter((p) => !p.is_add_on)
|
||||
.map(async (p) => {
|
||||
let prod = getProductResponse({ product: p, features });
|
||||
let curMainProduct, curScheduledProduct;
|
||||
let pricecnProds = await Promise.all(batchResponse);
|
||||
|
||||
if (cusProducts) {
|
||||
let res = getExistingCusProducts({
|
||||
product: p,
|
||||
cusProducts: cusProducts,
|
||||
});
|
||||
// let pricecnProds = await Promise.all(
|
||||
// products
|
||||
// // .filter((p) => !p.is_add_on)
|
||||
// .map(async (p) => {
|
||||
// let prod = getProductResponse({ product: p, features });
|
||||
// let curMainProduct, curScheduledProduct;
|
||||
|
||||
curMainProduct = res.curMainProduct;
|
||||
curScheduledProduct = res.curScheduledProduct;
|
||||
}
|
||||
// if (cusProducts) {
|
||||
// let res = getExistingCusProducts({
|
||||
// product: p,
|
||||
// cusProducts: cusProducts,
|
||||
// });
|
||||
|
||||
return toPricecnProduct({
|
||||
org,
|
||||
product: prod as ProductV2,
|
||||
fullProduct: p,
|
||||
features,
|
||||
curMainProduct,
|
||||
curScheduledProduct,
|
||||
otherProducts: products.filter((other) => other.id != p.id),
|
||||
});
|
||||
}),
|
||||
);
|
||||
// curMainProduct = res.curMainProduct;
|
||||
// curScheduledProduct = res.curScheduledProduct;
|
||||
// }
|
||||
|
||||
// return toPricecnProduct({
|
||||
// org,
|
||||
// product: prod as ProductV2,
|
||||
// fullProduct: p,
|
||||
// features,
|
||||
// curMainProduct,
|
||||
// curScheduledProduct,
|
||||
// otherProducts: products.filter((other) => other.id != p.id),
|
||||
// });
|
||||
// }),
|
||||
// );
|
||||
|
||||
res.status(200).json({
|
||||
list: pricecnProds,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { formatAmount } from "@/utils/formatUtils.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
|
||||
const getNextCycle = (preview: AttachPreview) => {
|
||||
if (!preview.due_next_cycle && !preview.due_today) {
|
||||
@@ -29,11 +30,13 @@ const getNextCycle = (preview: AttachPreview) => {
|
||||
};
|
||||
export const attachToCheckPreview = async ({
|
||||
preview,
|
||||
params,
|
||||
product,
|
||||
org,
|
||||
features,
|
||||
}: {
|
||||
preview: AttachPreview;
|
||||
params: AttachParams;
|
||||
product: FullProduct;
|
||||
org: Organization;
|
||||
features: Feature[];
|
||||
@@ -112,7 +115,11 @@ export const attachToCheckPreview = async ({
|
||||
items,
|
||||
due_today,
|
||||
due_next_cycle,
|
||||
product: getProductResponse({ product, features }),
|
||||
product: getProductResponse({
|
||||
product,
|
||||
features,
|
||||
trialAvailable: notNullish(params.freeTrial) ? true : false,
|
||||
}),
|
||||
};
|
||||
return checkPreview;
|
||||
};
|
||||
@@ -153,6 +160,7 @@ export const getProductCheckPreview = async ({
|
||||
|
||||
const checkPreview = await attachToCheckPreview({
|
||||
preview,
|
||||
params: attachParams,
|
||||
product,
|
||||
org,
|
||||
features,
|
||||
|
||||
@@ -84,16 +84,6 @@ export const handleProductCheck = async ({
|
||||
// })
|
||||
// : undefined;
|
||||
|
||||
if (preview) {
|
||||
preview = {
|
||||
...preview,
|
||||
product: getProductResponse({
|
||||
product: product!,
|
||||
features,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!cusProduct) {
|
||||
res.status(200).json({
|
||||
customer_id,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { AttachConfig } from "@autumn/shared";
|
||||
import { attachToInsertParams } from "@/internal/products/productUtils.js";
|
||||
import { insertInvoiceFromAttach } from "@/internal/invoices/invoiceUtils.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { isFixedPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
||||
|
||||
export const handleOneOffFunction = async ({
|
||||
req,
|
||||
@@ -56,32 +57,46 @@ export const handleOneOffFunction = async ({
|
||||
.toNumber();
|
||||
}
|
||||
|
||||
const amount = priceToInvoiceAmount({
|
||||
price,
|
||||
quantity,
|
||||
});
|
||||
let invoiceItemData = {};
|
||||
if (isFixedPrice({ price })) {
|
||||
quantity = 1;
|
||||
|
||||
const product = priceToProduct({
|
||||
price,
|
||||
products,
|
||||
});
|
||||
invoiceItemData = {
|
||||
price: price.config.stripe_price_id,
|
||||
quantity: 1,
|
||||
};
|
||||
} else {
|
||||
const amount = priceToInvoiceAmount({
|
||||
price,
|
||||
quantity,
|
||||
});
|
||||
|
||||
const description = newPriceToInvoiceDescription({
|
||||
org,
|
||||
price,
|
||||
product: product!,
|
||||
ents: entitlements,
|
||||
quantity: options?.quantity,
|
||||
withProductPrefix: true,
|
||||
});
|
||||
const product = priceToProduct({
|
||||
price,
|
||||
products,
|
||||
});
|
||||
|
||||
const description = newPriceToInvoiceDescription({
|
||||
org,
|
||||
price,
|
||||
product: product!,
|
||||
ents: entitlements,
|
||||
quantity: options?.quantity,
|
||||
withProductPrefix: true,
|
||||
});
|
||||
|
||||
invoiceItemData = {
|
||||
description,
|
||||
price_data: {
|
||||
unit_amount: new Decimal(amount).mul(100).round().toNumber(),
|
||||
currency: org.default_currency,
|
||||
product: price.config?.stripe_product_id || product?.processor?.id!,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
invoiceItems.push({
|
||||
description,
|
||||
price_data: {
|
||||
unit_amount: new Decimal(amount).mul(100).round().toNumber(),
|
||||
currency: org.default_currency,
|
||||
product: price.config?.stripe_product_id || product?.processor?.id!,
|
||||
},
|
||||
...invoiceItemData,
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,18 +5,8 @@ import {
|
||||
CreateFreeTrialSchema,
|
||||
FreeTrial,
|
||||
FreeTrialDuration,
|
||||
FullProduct,
|
||||
} from "@autumn/shared";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import {
|
||||
addDays,
|
||||
addMinutes,
|
||||
addMonths,
|
||||
addSeconds,
|
||||
addWeeks,
|
||||
addYears,
|
||||
getTime,
|
||||
} from "date-fns";
|
||||
import { addDays, addMinutes, addMonths, addYears } from "date-fns";
|
||||
import { FreeTrialService } from "./FreeTrialService.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
|
||||
@@ -15,12 +15,16 @@ import {
|
||||
numberWithCommas,
|
||||
AttachScenario,
|
||||
FullProduct,
|
||||
FullCustomer,
|
||||
FreeTrialResponseSchema,
|
||||
} from "@autumn/shared";
|
||||
import { isPriceItem } from "../product-items/productItemUtils/getItemType.js";
|
||||
import { isFeaturePriceItem } from "../product-items/productItemUtils/getItemType.js";
|
||||
import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { isProductUpgrade } from "../productUtils.js";
|
||||
import { getFirstInterval } from "../prices/priceUtils/priceIntervalUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { getFreeTrialAfterFingerprint } from "../free-trials/freeTrialUtils.js";
|
||||
|
||||
export const sortProductItems = (items: ProductItem[], features: Feature[]) => {
|
||||
items.sort((a, b) => {
|
||||
@@ -287,7 +291,8 @@ export const getAttachScenario = ({
|
||||
return isUpgrade ? AttachScenario.Upgrade : AttachScenario.Downgrade;
|
||||
};
|
||||
|
||||
export const toPricecnProduct = ({
|
||||
export const toPricecnProduct = async ({
|
||||
db,
|
||||
org,
|
||||
product,
|
||||
fullProduct,
|
||||
@@ -295,7 +300,9 @@ export const toPricecnProduct = ({
|
||||
features,
|
||||
curMainProduct,
|
||||
curScheduledProduct,
|
||||
fullCus,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
org: Organization;
|
||||
product: ProductV2;
|
||||
fullProduct: FullProduct;
|
||||
@@ -303,6 +310,7 @@ export const toPricecnProduct = ({
|
||||
features: Feature[];
|
||||
curMainProduct?: FullCusProduct | null;
|
||||
curScheduledProduct?: FullCusProduct | null;
|
||||
fullCus?: FullCustomer;
|
||||
}) => {
|
||||
let items = structuredClone(product.items);
|
||||
|
||||
@@ -389,6 +397,21 @@ export const toPricecnProduct = ({
|
||||
intervalGroup = getFirstInterval({ prices: fullProduct.prices });
|
||||
}
|
||||
|
||||
let trialAvailable = false;
|
||||
if (product.free_trial && fullCus) {
|
||||
let trial = await getFreeTrialAfterFingerprint({
|
||||
db,
|
||||
freeTrial: product.free_trial,
|
||||
fingerprint: fullCus.fingerprint,
|
||||
internalCustomerId: fullCus.internal_id,
|
||||
multipleAllowed: org.config.multiple_trials,
|
||||
productId: product.id,
|
||||
});
|
||||
|
||||
if (scenario == AttachScenario.Downgrade) trial = null;
|
||||
trialAvailable = notNullish(trial) ? true : false;
|
||||
}
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
name,
|
||||
@@ -406,10 +429,10 @@ export const toPricecnProduct = ({
|
||||
scenario,
|
||||
button_text: buttonText,
|
||||
free_trial: freeTrial
|
||||
? {
|
||||
length: freeTrial,
|
||||
interval: freeTrial.duration,
|
||||
}
|
||||
? FreeTrialResponseSchema.parse({
|
||||
...freeTrial,
|
||||
trial_available: trialAvailable,
|
||||
})
|
||||
: null,
|
||||
|
||||
interval_group: intervalGroup,
|
||||
|
||||
@@ -59,7 +59,10 @@ export const detectBaseVariant = async ({
|
||||
nullish(p.base_variant_id) &&
|
||||
!p.is_add_on &&
|
||||
p.prices.length > 0 &&
|
||||
p.prices.every((price) => price.config.interval == BillingInterval.Month),
|
||||
p.prices.every(
|
||||
(price) => price.config.interval == BillingInterval.Month,
|
||||
) &&
|
||||
p.group == curProduct.group,
|
||||
);
|
||||
|
||||
if (filteredExistingProducts.length == 0) return null;
|
||||
|
||||
@@ -2,6 +2,8 @@ import {
|
||||
EntitlementWithFeature,
|
||||
Feature,
|
||||
FeatureType,
|
||||
FreeTrial,
|
||||
FreeTrialResponseSchema,
|
||||
FullProduct,
|
||||
Price,
|
||||
ProductItem,
|
||||
@@ -109,9 +111,11 @@ export const mapToProductV2 = ({
|
||||
export const getProductResponse = ({
|
||||
product,
|
||||
features,
|
||||
trialAvailable,
|
||||
}: {
|
||||
product: FullProduct;
|
||||
features: Feature[];
|
||||
trialAvailable?: boolean;
|
||||
}) => {
|
||||
let items = mapToProductItems({
|
||||
prices: product.prices,
|
||||
@@ -134,5 +138,13 @@ export const getProductResponse = ({
|
||||
name: product.name || null,
|
||||
group: product.group || null,
|
||||
items: items,
|
||||
free_trial: product.free_trial
|
||||
? FreeTrialResponseSchema.parse({
|
||||
duration: product.free_trial?.duration,
|
||||
length: product.free_trial?.length,
|
||||
unique_fingerprint: product.free_trial?.unique_fingerprint,
|
||||
trial_available: trialAvailable,
|
||||
})
|
||||
: null,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ export const FreeTrialResponseSchema = z.object({
|
||||
duration: z.nativeEnum(FreeTrialDuration),
|
||||
length: z.number(),
|
||||
unique_fingerprint: z.boolean(),
|
||||
trial_available: z.boolean().nullish().default(true),
|
||||
});
|
||||
|
||||
export type FreeTrial = z.infer<typeof FreeTrialSchema>;
|
||||
|
||||
Reference in New Issue
Block a user