fix: entity and customer.products.updated webhooks
This commit is contained in:
@@ -61,9 +61,36 @@ autumnWebhookRouter.post(
|
||||
|
||||
switch (type) {
|
||||
case WebhookEventType.CustomerProductsUpdated:
|
||||
console.log(`--------------------------------`);
|
||||
// console.log(`Received customer.products.updated webhook`);
|
||||
// console.log(JSON.stringify(data, null, 2));
|
||||
console.log(
|
||||
`Type: ${type}, Scenario: ${data?.scenario}, Product: ${data?.updated_product?.id}`
|
||||
`Customer:`,
|
||||
data?.customer.id,
|
||||
`Products:`,
|
||||
data?.customer.products.map((p: any) => ({
|
||||
id: p.id,
|
||||
entity_id: p.entity_id,
|
||||
status: p.status,
|
||||
quantity: p.quantity,
|
||||
}))
|
||||
);
|
||||
|
||||
if (data?.entity) {
|
||||
console.log(
|
||||
`Entity: ${data.entity.id}, Products:`,
|
||||
data.entity.products.map((p: any) => ({
|
||||
id: p.id,
|
||||
status: p.status,
|
||||
quantity: p.quantity,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Update product ID: ${data?.updated_product?.id}, Scenario: ${data?.scenario}`
|
||||
);
|
||||
console.log(`--------------------------------`);
|
||||
break;
|
||||
case WebhookEventType.CustomerThresholdReached:
|
||||
console.log(`Type: ${type}`);
|
||||
|
||||
@@ -20,11 +20,12 @@ export const priceToOneOffAndTiered = ({
|
||||
}) => {
|
||||
const config = price.config as UsagePriceConfig;
|
||||
let quantity = options?.quantity!;
|
||||
let overage = quantity * config.billing_units! - relatedEnt.allowance!;
|
||||
let overage = new Decimal(quantity).mul(config.billing_units!).toNumber();
|
||||
// let overage = quantity * config.billing_units! - relatedEnt.allowance!;
|
||||
|
||||
if (overage <= 0) {
|
||||
return null;
|
||||
}
|
||||
// if (overage <= 0) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
const amount = getPriceForOverage(price, overage);
|
||||
if (!config.stripe_product_id) {
|
||||
|
||||
@@ -125,6 +125,7 @@ export const handleCheckoutSessionCompleted = async ({
|
||||
const anchorToUnix = checkoutSub
|
||||
? getEarliestPeriodEnd({ sub: checkoutSub! }) * 1000
|
||||
: undefined;
|
||||
|
||||
if (attachParams.productsList) {
|
||||
console.log("Inserting products list");
|
||||
for (const productOptions of attachParams.productsList) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import {
|
||||
formatPrice,
|
||||
getBillingType,
|
||||
getPriceEntitlement,
|
||||
priceIsOneOffAndTiered,
|
||||
@@ -32,9 +33,7 @@ export const getOptionsFromCheckoutSession = async ({
|
||||
for (const price of prices) {
|
||||
let config = price.config as UsagePriceConfig;
|
||||
|
||||
if (getBillingType(config) != BillingType.UsageInAdvance) {
|
||||
continue;
|
||||
}
|
||||
if (getBillingType(config) != BillingType.UsageInAdvance) continue;
|
||||
|
||||
const lineItem = findStripeItemForPrice({
|
||||
price,
|
||||
@@ -47,7 +46,8 @@ export const getOptionsFromCheckoutSession = async ({
|
||||
let relatedEnt = getPriceEntitlement(price, ents);
|
||||
|
||||
if (priceIsOneOffAndTiered(price, relatedEnt)) {
|
||||
quantity = (lineItem.quantity || 0) + (relatedEnt.allowance || 0);
|
||||
// quantity = lineItem.quantity || 0;
|
||||
continue;
|
||||
} else {
|
||||
quantity = lineItem.quantity || 0;
|
||||
}
|
||||
|
||||
@@ -121,11 +121,10 @@ export const handleSchedulePhaseCompleted = async ({
|
||||
scheduled_ids: [],
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`schedule.phase.completed: failed to cancel schedule ${schedule.id}`
|
||||
`schedule.phase.completed: failed to cancel schedule ${schedule.id}, error: ${error.message}`
|
||||
);
|
||||
logger.error({ error });
|
||||
}
|
||||
}
|
||||
// const currentPhase = schedule.phases.find(
|
||||
|
||||
@@ -102,9 +102,7 @@ export const handleSubCanceled = async ({
|
||||
|
||||
const { db, env, logtail: logger } = req;
|
||||
|
||||
if (!canceledFromPortal || updatedCusProducts.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (!canceledFromPortal || updatedCusProducts.length == 0) return;
|
||||
|
||||
await updateCusProductCanceled({
|
||||
db,
|
||||
@@ -194,6 +192,8 @@ export const handleSubCanceled = async ({
|
||||
}
|
||||
|
||||
for (let cusProd of updatedCusProducts) {
|
||||
console.log("Sending webhook for canceled product: ", cusProd.product.id);
|
||||
|
||||
try {
|
||||
await addProductsUpdatedWebhookTask({
|
||||
req,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { AttachScenario, FullCusProduct } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { isMultiProductSub } from "@/internal/customers/attach/mergeUtils/mergeUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { getSubScenarioFromCache } from "@/internal/customers/cusCache/subCacheUtils.js";
|
||||
const isSubRenewed = ({
|
||||
previousAttributes,
|
||||
sub,
|
||||
@@ -70,12 +71,18 @@ export const handleSubRenewed = async ({
|
||||
|
||||
if (!renewed || updatedCusProducts.length == 0) return;
|
||||
|
||||
const subScenario = await getSubScenarioFromCache({ subId: sub.id });
|
||||
console.log(`Renewed: ${renewed}, subScenario: ${subScenario}`);
|
||||
if (subScenario === AttachScenario.Renew) return;
|
||||
|
||||
const customer = updatedCusProducts[0].customer;
|
||||
let cusProducts = await CusProductService.list({
|
||||
db,
|
||||
internalCustomerId: customer!.internal_id,
|
||||
});
|
||||
|
||||
console.log(`handling sub.renewed!`);
|
||||
|
||||
if (isMultiProductSub({ sub, cusProducts }) || sub.schedule) return;
|
||||
|
||||
await CusProductService.updateByStripeSubId({
|
||||
|
||||
@@ -2,9 +2,9 @@ import {
|
||||
ActionType,
|
||||
AppEnv,
|
||||
AuthType,
|
||||
CusProductStatus,
|
||||
FullCusProduct,
|
||||
FullProduct,
|
||||
notNullish,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
@@ -23,7 +23,8 @@ import { ActionService } from "@/internal/analytics/ActionService.js";
|
||||
import { constructAction } from "@/internal/analytics/actionUtils.js";
|
||||
import { parseReqForAction } from "@/internal/analytics/actionUtils.js";
|
||||
import { RELEVANT_STATUSES } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { cusProductToPrices, cusProductToProduct } from "@autumn/shared";
|
||||
import { cusProductToProduct } from "@autumn/shared";
|
||||
import { getSingleEntityResponse } from "@/internal/api/entities/getEntityUtils.js";
|
||||
|
||||
interface ActionDetails {
|
||||
request_id: string;
|
||||
@@ -121,19 +122,8 @@ export const handleProductsUpdated = async ({
|
||||
|
||||
// Product:
|
||||
let product = cusProduct.product;
|
||||
// const prices = cusProductToPrices({ cusProduct });
|
||||
// const ents = cusProductToEnts({ cusProduct });
|
||||
// let freeTrial = cusProduct.free_trial;
|
||||
|
||||
let fullProduct: FullProduct = cusProductToProduct({ cusProduct });
|
||||
|
||||
// {
|
||||
// ...product,
|
||||
// prices,
|
||||
// entitlements: ents,
|
||||
// free_trial: freeTrial || null,
|
||||
// };
|
||||
|
||||
let customer = await CusService.getFull({
|
||||
db,
|
||||
idOrInternalId: data.customerId || data.internalCustomerId,
|
||||
@@ -165,9 +155,6 @@ export const handleProductsUpdated = async ({
|
||||
features,
|
||||
});
|
||||
|
||||
// 1. Log action to DB
|
||||
|
||||
// console.log(`handling products.updated for customer ${customer.id}`);
|
||||
try {
|
||||
if (req) {
|
||||
let action = constructAction({
|
||||
@@ -205,6 +192,28 @@ export const handleProductsUpdated = async ({
|
||||
}
|
||||
}
|
||||
|
||||
let entityRes = null;
|
||||
if (notNullish(customer?.entity)) {
|
||||
entityRes = await getSingleEntityResponse({
|
||||
entityId: customer.entity!.id,
|
||||
org,
|
||||
env,
|
||||
fullCus: customer,
|
||||
entity: customer.entity!,
|
||||
features,
|
||||
});
|
||||
}
|
||||
|
||||
// console.log(`Sending svix event for customer ${customer.id}`);
|
||||
// console.log(
|
||||
// "Products:",
|
||||
// cusDetails.products.map((p) => ({
|
||||
// id: p.id,
|
||||
// status: p.status,
|
||||
// quantity: p.quantity,
|
||||
// }))
|
||||
// );
|
||||
|
||||
// 2. Send Svix event
|
||||
await sendSvixEvent({
|
||||
org,
|
||||
@@ -213,6 +222,7 @@ export const handleProductsUpdated = async ({
|
||||
data: {
|
||||
scenario,
|
||||
customer: cusDetails,
|
||||
entity: entityRes,
|
||||
updated_product: productRes,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -43,11 +43,11 @@ export const runActionHandlerTask = async ({
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
logger.error("Error processing action handler job:", {
|
||||
jobName: job.name,
|
||||
error,
|
||||
payload,
|
||||
// jobName: job.name,
|
||||
// payload,
|
||||
message: error.message,
|
||||
});
|
||||
} finally {
|
||||
await releaseLock({ lockKey, useBackup });
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { getCusWithCache } from "@/internal/customers/cusCache/getCusWithCache.js";
|
||||
import {
|
||||
ACTIVE_STATUSES,
|
||||
RELEVANT_STATUSES,
|
||||
} from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { getCusFeaturesResponse } from "@/internal/customers/cusUtils/cusFeatureResponseUtils/getCusFeaturesResponse.js";
|
||||
import { processFullCusProducts } from "@/internal/customers/cusUtils/cusProductResponseUtils/processFullCusProducts.js";
|
||||
|
||||
@@ -13,7 +8,6 @@ import { nullish } from "@/utils/genUtils.js";
|
||||
import {
|
||||
type AppEnv,
|
||||
Feature,
|
||||
CusProductStatus,
|
||||
type Entity,
|
||||
EntityExpand,
|
||||
type EntityResponse,
|
||||
@@ -32,6 +26,7 @@ export const getSingleEntityResponse = async ({
|
||||
org,
|
||||
env,
|
||||
fullCus,
|
||||
entity,
|
||||
features,
|
||||
withAutumnId = false,
|
||||
}: {
|
||||
@@ -39,13 +34,10 @@ export const getSingleEntityResponse = async ({
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
fullCus: FullCustomer;
|
||||
entity: Entity;
|
||||
features: Feature[];
|
||||
withAutumnId?: boolean;
|
||||
}) => {
|
||||
let entity = fullCus.entities.find(
|
||||
(e: Entity) => e.id == entityId || e.internal_id == entityId
|
||||
);
|
||||
|
||||
const apiVersion = APIVersion.v1_2;
|
||||
|
||||
if (!entity) {
|
||||
@@ -80,7 +72,7 @@ export const getSingleEntityResponse = async ({
|
||||
|
||||
let { main, addOns } = await processFullCusProducts({
|
||||
fullCusProducts: entityCusProducts,
|
||||
entities: fullCus.entities,
|
||||
entity,
|
||||
subs: entitySubs,
|
||||
org,
|
||||
apiVersion: APIVersion.v1_2,
|
||||
@@ -136,7 +128,7 @@ export const getEntityResponse = async ({
|
||||
logger: any;
|
||||
skipCache?: boolean;
|
||||
}) => {
|
||||
let customer = await getCusWithCache({
|
||||
let fullCus = await getCusWithCache({
|
||||
db,
|
||||
idOrInternalId: customerId,
|
||||
org,
|
||||
@@ -147,7 +139,7 @@ export const getEntityResponse = async ({
|
||||
skipCache,
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
if (!fullCus) {
|
||||
throw new RecaseError({
|
||||
message: `Customer ${customerId} not found`,
|
||||
code: ErrCode.CustomerNotFound,
|
||||
@@ -157,11 +149,18 @@ export const getEntityResponse = async ({
|
||||
|
||||
const entityResponses: EntityResponse[] = [];
|
||||
for (const entityId of entityIds) {
|
||||
const entity = fullCus.entities.find(
|
||||
(e: Entity) => e.id == entityId || e.internal_id == entityId
|
||||
);
|
||||
|
||||
if (!entity) continue;
|
||||
|
||||
let entityResponse = await getSingleEntityResponse({
|
||||
entityId,
|
||||
org,
|
||||
env,
|
||||
fullCus: customer,
|
||||
fullCus,
|
||||
entity,
|
||||
features,
|
||||
withAutumnId,
|
||||
});
|
||||
@@ -171,8 +170,8 @@ export const getEntityResponse = async ({
|
||||
|
||||
return {
|
||||
entities: entityResponses,
|
||||
customer,
|
||||
fullEntities: customer.entities,
|
||||
invoices: customer.invoices,
|
||||
customer: fullCus,
|
||||
fullEntities: fullCus.entities,
|
||||
invoices: fullCus.invoices,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -399,6 +399,7 @@ export const createFullCusProduct = async ({
|
||||
}
|
||||
|
||||
// 3. Deduct existing usages
|
||||
|
||||
let deductedCusEnts = addExistingUsagesToCusEnts({
|
||||
cusEnts: cusEnts,
|
||||
entitlements: entitlements,
|
||||
|
||||
@@ -80,6 +80,7 @@ export const handlePaidProduct = async ({
|
||||
let trialEndsAt = undefined;
|
||||
|
||||
// 1. If merge sub
|
||||
|
||||
if (mergeSub && !config.disableMerge) {
|
||||
if (mergeCusProduct?.free_trial) {
|
||||
trialEndsAt = isTrialing({
|
||||
@@ -152,6 +153,7 @@ export const handlePaidProduct = async ({
|
||||
billingCycleAnchorUnix = attachParams.billingAnchor;
|
||||
}
|
||||
|
||||
// console.log("Item set: ", itemSet);
|
||||
try {
|
||||
sub = await createStripeSub2({
|
||||
db: req.db,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
paramsToCurSub,
|
||||
} from "../attachUtils/convertAttachParams.js";
|
||||
import { paramsToScheduleItems } from "../mergeUtils/paramsToScheduleItems.js";
|
||||
import { AttachConfig, SuccessCode } from "@autumn/shared";
|
||||
import { AttachConfig, AttachScenario, SuccessCode } from "@autumn/shared";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import {
|
||||
cusProductToSchedule,
|
||||
@@ -19,6 +19,8 @@ import { subToNewSchedule } from "../mergeUtils/subToNewSchedule.js";
|
||||
import { getLatestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
|
||||
import { updateCurSchedule } from "../mergeUtils/updateCurSchedule.js";
|
||||
import { subItemInCusProduct } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { addSubIdToCache } from "../../cusCache/subCacheUtils.js";
|
||||
|
||||
export const handleRenewProduct = async ({
|
||||
req,
|
||||
@@ -33,9 +35,9 @@ export const handleRenewProduct = async ({
|
||||
}) => {
|
||||
const logger = req.logtail;
|
||||
const { stripeCli, customer: fullCus } = attachParams;
|
||||
const { curScheduledProduct } = attachParamToCusProducts({ attachParams });
|
||||
|
||||
const curCusProduct = attachParamsToCurCusProduct({ attachParams });
|
||||
const { curScheduledProduct } = attachParamToCusProducts({ attachParams });
|
||||
const product = attachParams.products[0];
|
||||
const cusProducts = attachParams.customer.customer_products;
|
||||
|
||||
@@ -91,6 +93,11 @@ export const handleRenewProduct = async ({
|
||||
}
|
||||
|
||||
if (curSubId) {
|
||||
// Add sub id to upstash
|
||||
await addSubIdToCache({
|
||||
subId: curSubId,
|
||||
scenario: AttachScenario.Renew,
|
||||
});
|
||||
await stripeCli.subscriptions.update(curSubId, {
|
||||
cancel_at: null,
|
||||
});
|
||||
@@ -193,6 +200,24 @@ export const handleRenewProduct = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (curCusProduct) {
|
||||
try {
|
||||
await addProductsUpdatedWebhookTask({
|
||||
req,
|
||||
internalCustomerId: curCusProduct.internal_customer_id,
|
||||
org: attachParams.org,
|
||||
env: attachParams.customer.env,
|
||||
customerId:
|
||||
attachParams.customer.id || attachParams.customer.internal_id,
|
||||
scenario: AttachScenario.Renew,
|
||||
cusProduct: curCusProduct,
|
||||
logger,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("RENEW FLOW: failed to add to webhook queue", { error });
|
||||
}
|
||||
}
|
||||
|
||||
if (curScheduledProduct) {
|
||||
await CusProductService.delete({
|
||||
db: req.db,
|
||||
|
||||
@@ -26,6 +26,7 @@ import { subToNewSchedule } from "../../mergeUtils/subToNewSchedule.js";
|
||||
import { updateCurSchedule } from "../../mergeUtils/updateCurSchedule.js";
|
||||
import { subItemInCusProduct } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
|
||||
import { getCurrentPhaseIndex } from "../../mergeUtils/phaseUtils/phaseUtils.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
|
||||
export const handleScheduleFunction2 = async ({
|
||||
req,
|
||||
@@ -138,6 +139,9 @@ export const handleScheduleFunction2 = async ({
|
||||
logger.info(`SCHEDULE FLOW: no schedule, canceling sub ${curSub?.id}`);
|
||||
await stripeCli.subscriptions.update(curSub!.id, {
|
||||
cancel_at: expectedEnd!,
|
||||
cancellation_details: {
|
||||
comment: "autumn_downgrade",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,13 +154,36 @@ export const handleScheduleFunction2 = async ({
|
||||
nextResetAt: expectedEnd! * 1000,
|
||||
disableFreeTrial: true,
|
||||
isDowngrade: true,
|
||||
scenario: newProductFree
|
||||
? AttachScenario.Cancel
|
||||
: AttachScenario.Downgrade,
|
||||
sendWebhook: false,
|
||||
// scenario: newProductFree
|
||||
// ? AttachScenario.Cancel
|
||||
// : AttachScenario.Downgrade,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
||||
if (curCusProduct) {
|
||||
try {
|
||||
await addProductsUpdatedWebhookTask({
|
||||
req,
|
||||
internalCustomerId: curCusProduct.internal_customer_id,
|
||||
org: attachParams.org,
|
||||
env: attachParams.customer.env,
|
||||
customerId:
|
||||
attachParams.customer.id || attachParams.customer.internal_id,
|
||||
|
||||
scenario: isFreeProduct(attachParams.prices)
|
||||
? AttachScenario.Cancel
|
||||
: AttachScenario.Downgrade,
|
||||
|
||||
cusProduct: curCusProduct,
|
||||
logger,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("SCHEDULE FLOW: failed to add to webhook queue", { error });
|
||||
}
|
||||
}
|
||||
|
||||
let apiVersion = attachParams.apiVersion || APIVersion.v1;
|
||||
|
||||
if (res) {
|
||||
|
||||
@@ -207,6 +207,7 @@ export const handleUpgradeFlow = async ({
|
||||
carryExistingUsages: config.carryUsage,
|
||||
carryOverTrial: config.carryTrial,
|
||||
anchorToUnix: anchorToUnix,
|
||||
scenario: AttachScenario.Upgrade,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -184,8 +184,6 @@ export const handleCheckout = (req: any, res: any) =>
|
||||
|
||||
if (checkoutRes.next_cycle) {
|
||||
const nextCycle = checkoutRes.next_cycle;
|
||||
console.log("Due at:", formatUnixToDate(nextCycle.starts_at!));
|
||||
console.log("Total:", nextCycle.total);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
|
||||
21
server/src/internal/customers/cusCache/subCacheUtils.ts
Normal file
21
server/src/internal/customers/cusCache/subCacheUtils.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { initUpstash } from "./upstashUtils.js";
|
||||
|
||||
export const addSubIdToCache = async ({
|
||||
subId,
|
||||
scenario,
|
||||
}: {
|
||||
subId: string;
|
||||
scenario: string;
|
||||
}) => {
|
||||
const upstash = await initUpstash();
|
||||
if (!upstash) return;
|
||||
|
||||
await upstash.set(`sub:${subId}`, scenario, {
|
||||
ex: 180, // 3 minutes
|
||||
});
|
||||
};
|
||||
export const getSubScenarioFromCache = async ({ subId }: { subId: string }) => {
|
||||
const upstash = await initUpstash();
|
||||
if (!upstash) return null;
|
||||
return (await upstash.get(`sub:${subId}`)) as string | null;
|
||||
};
|
||||
@@ -119,132 +119,6 @@ export const getCusEntBalance = ({
|
||||
};
|
||||
};
|
||||
|
||||
// export const sortCusEntsForDeduction = (
|
||||
// cusEnts: (FullCustomerEntitlement & {
|
||||
// customer_product?: FullCusProduct;
|
||||
// })[],
|
||||
// reverseOrder: boolean = false
|
||||
// ) => {
|
||||
// let intervalOrder: Record<EntInterval, number> = {
|
||||
// [EntInterval.Minute]: 0, // 1 minute
|
||||
// [EntInterval.Hour]: 1, // 1 hour
|
||||
// [EntInterval.Day]: 2, // 1 day
|
||||
// [EntInterval.Week]: 3, // 1 week
|
||||
// [EntInterval.Month]: 4, // 1 month
|
||||
// [EntInterval.Quarter]: 5, // 3 months
|
||||
// [EntInterval.Year]: 6, // 1 year
|
||||
// [EntInterval.SemiAnnual]: 7, // 6 months
|
||||
// [EntInterval.Lifetime]: 8, // 1 time
|
||||
// };
|
||||
|
||||
// // console.log(
|
||||
// // `Cus ents before (${reverseOrder ? "reversed" : "normal"})`,
|
||||
// // cusEnts.map(
|
||||
// // (ce) => `${ce.entitlement.feature_id} - ${ce.entitlement.interval}`
|
||||
// // )
|
||||
// // );
|
||||
// cusEnts.sort((a, b) => {
|
||||
// const aEnt = a.entitlement;
|
||||
// const bEnt = b.entitlement;
|
||||
|
||||
// // 1. If boolean, go first
|
||||
// if (aEnt.feature.type == FeatureType.Boolean) {
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// if (bEnt.feature.type == FeatureType.Boolean) {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// // 1. If a is credit system and b is not, a should go last
|
||||
// if (
|
||||
// aEnt.feature.type == FeatureType.CreditSystem &&
|
||||
// bEnt.feature.type != FeatureType.CreditSystem
|
||||
// ) {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// // 2. If a is not credit system and b is, a should go first
|
||||
// if (
|
||||
// aEnt.feature.type != FeatureType.CreditSystem &&
|
||||
// bEnt.feature.type == FeatureType.CreditSystem
|
||||
// ) {
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// // 2. Sort by unlimited (unlimited goes first)
|
||||
// if (
|
||||
// aEnt.allowance_type == AllowanceType.Unlimited &&
|
||||
// bEnt.allowance_type != AllowanceType.Unlimited
|
||||
// ) {
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// if (
|
||||
// aEnt.allowance_type != AllowanceType.Unlimited &&
|
||||
// bEnt.allowance_type == AllowanceType.Unlimited
|
||||
// ) {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// // If one has usage_allowed, it should go last
|
||||
// if (!a.usage_allowed && b.usage_allowed) {
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// if (!b.usage_allowed && a.usage_allowed) {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// // If one has a next_reset_at, it should go first
|
||||
// let nextResetFirst = reverseOrder ? 1 : -1;
|
||||
|
||||
// if (a.next_reset_at && !b.next_reset_at) {
|
||||
// return nextResetFirst;
|
||||
// }
|
||||
|
||||
// // If b has a next_reset_at, it should go first
|
||||
// if (!a.next_reset_at && b.next_reset_at) {
|
||||
// return -nextResetFirst;
|
||||
// }
|
||||
|
||||
// // 3. Sort by interval
|
||||
// let aVal = entIntervalToValue(aEnt.interval, aEnt.interval_count);
|
||||
// let bVal = entIntervalToValue(bEnt.interval, bEnt.interval_count);
|
||||
// if (aEnt.interval && bEnt.interval && !aVal.eq(bVal)) {
|
||||
// if (reverseOrder) {
|
||||
// return bVal.sub(aVal).toNumber();
|
||||
// // return intervalOrder[bEnt.interval] - intervalOrder[aEnt.interval];
|
||||
// } else {
|
||||
// return aVal.sub(bVal).toNumber();
|
||||
// // return intervalOrder[aEnt.interval] - intervalOrder[bEnt.interval];
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Check if a is main product
|
||||
// let aIsAddOn = a.customer_product?.product?.is_add_on;
|
||||
// let bIsAddOn = b.customer_product?.product?.is_add_on;
|
||||
|
||||
// if (aIsAddOn && !bIsAddOn) {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// if (!aIsAddOn && bIsAddOn) {
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// // 4. Sort by created_at
|
||||
// return a.created_at - b.created_at;
|
||||
// });
|
||||
|
||||
// // console.log(
|
||||
// // `Cus ents after (${reverseOrder ? "reversed" : "normal"})`,
|
||||
// // cusEnts.map(
|
||||
// // (ce) => `${ce.entitlement.feature_id} - ${ce.entitlement.interval}`
|
||||
// // )
|
||||
// // );
|
||||
// };
|
||||
|
||||
// Get related cusPrice
|
||||
export const getRelatedCusPrice = (
|
||||
cusEnt: FullCustomerEntitlement,
|
||||
|
||||
@@ -221,7 +221,7 @@ export const addExistingUsagesToCusEnts = ({
|
||||
|
||||
for (const cusEnt of fullCusEnts) {
|
||||
let ent = cusEnt.entitlement;
|
||||
// let cusEntKey = `${ent.feature_id}-${ent.interval}-${ent.interval_count || 1}`;
|
||||
|
||||
let fromEntities = existingUsages[key].fromEntities;
|
||||
|
||||
// if (cusEntKey !== key) continue;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
import { ErrCode, FullCustomerEntitlement } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { FullCustomerEntitlement } from "@autumn/shared";
|
||||
|
||||
export const getEntityBalance = ({
|
||||
cusEnt,
|
||||
@@ -13,14 +12,7 @@ export const getEntityBalance = ({
|
||||
let entityBalance = cusEnt.entities?.[entityId!]?.balance;
|
||||
let adjustment = cusEnt.entities?.[entityId!]?.adjustment || 0;
|
||||
|
||||
if (nullish(entityBalance)) {
|
||||
return { balance: 0, adjustment: 0 };
|
||||
// throw new RecaseError({
|
||||
// message: `Entity balance not found for entityId: ${entityId}`,
|
||||
// code: ErrCode.EntityBalanceNotFound,
|
||||
// statusCode: StatusCodes.BAD_REQUEST,
|
||||
// });
|
||||
}
|
||||
if (nullish(entityBalance)) return { balance: 0, adjustment: 0 };
|
||||
|
||||
return {
|
||||
balance: entityBalance,
|
||||
@@ -45,11 +37,11 @@ export const getSummedEntityBalances = ({
|
||||
return {
|
||||
balance: Object.values(cusEnt.entities!).reduce(
|
||||
(acc, curr) => acc + curr.balance,
|
||||
0,
|
||||
0
|
||||
),
|
||||
adjustment: Object.values(cusEnt.entities!).reduce(
|
||||
(acc, curr) => acc + curr.adjustment,
|
||||
0,
|
||||
0
|
||||
),
|
||||
unused: 0,
|
||||
count: Object.values(cusEnt.entities!).length,
|
||||
|
||||
@@ -15,11 +15,11 @@ import { notNullish, notNullOrUndefined } from "@/utils/genUtils.js";
|
||||
|
||||
import { BREAK_API_VERSION } from "@/utils/constants.js";
|
||||
import {
|
||||
getCusEntBalance,
|
||||
getRelatedCusPrice,
|
||||
getResetBalance,
|
||||
getUnlimitedAndUsageAllowed,
|
||||
} from "../../cusProducts/cusEnts/cusEntUtils.js";
|
||||
import { getCusEntBalance } from "@autumn/shared";
|
||||
|
||||
export interface CusFeatureBalance {
|
||||
feature_id: string;
|
||||
|
||||
@@ -41,16 +41,16 @@ export const getCusProductResponse = async ({
|
||||
cusProduct,
|
||||
subs,
|
||||
org,
|
||||
entities = [],
|
||||
apiVersion,
|
||||
features,
|
||||
entity,
|
||||
}: {
|
||||
cusProduct: FullCusProduct;
|
||||
org: Organization;
|
||||
subs?: Subscription[];
|
||||
entities?: Entity[];
|
||||
apiVersion: number;
|
||||
features: Feature[];
|
||||
entity?: Entity;
|
||||
}) => {
|
||||
// Process prices
|
||||
|
||||
@@ -184,11 +184,12 @@ export const getCusProductResponse = async ({
|
||||
|
||||
// stripe_subscription_ids: cusProduct.subscription_ids || [],
|
||||
started_at: cusProduct.starts_at,
|
||||
entity_id: cusProduct.internal_entity_id
|
||||
? entities?.find(
|
||||
(e: Entity) => e.internal_id == cusProduct.internal_entity_id
|
||||
)?.id
|
||||
: cusProduct.entity_id || undefined,
|
||||
entity_id: entity?.id || cusProduct.entity_id || undefined,
|
||||
// entity_id: cusProduct.internal_entity_id
|
||||
// ? entities?.find(
|
||||
// (e: Entity) => e.internal_id == cusProduct.internal_entity_id
|
||||
// )?.id
|
||||
// : cusProduct.entity_id || undefined,
|
||||
|
||||
...stripeSubData,
|
||||
items: v2Product.items,
|
||||
|
||||
@@ -48,14 +48,14 @@ export const processFullCusProducts = async ({
|
||||
fullCusProducts,
|
||||
subs,
|
||||
org,
|
||||
entities = [],
|
||||
entity,
|
||||
apiVersion,
|
||||
features,
|
||||
}: {
|
||||
fullCusProducts: any;
|
||||
subs: any;
|
||||
org: Organization;
|
||||
entities?: Entity[];
|
||||
entity?: Entity;
|
||||
apiVersion: number;
|
||||
features: Feature[];
|
||||
}) => {
|
||||
@@ -67,7 +67,7 @@ export const processFullCusProducts = async ({
|
||||
cusProduct,
|
||||
subs,
|
||||
org,
|
||||
entities,
|
||||
entity,
|
||||
apiVersion,
|
||||
features,
|
||||
});
|
||||
|
||||
@@ -82,7 +82,6 @@ export const getCustomerDetails = async ({
|
||||
subs,
|
||||
org,
|
||||
apiVersion,
|
||||
entities: customer.entities,
|
||||
features,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,40 @@ import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import Stripe from "stripe";
|
||||
|
||||
const createDefaultBillingPortalConfiguration = async (stripeCli: Stripe) => {
|
||||
try {
|
||||
const configuration = await stripeCli.billingPortal.configurations.create({
|
||||
features: {
|
||||
customer_update: {
|
||||
allowed_updates: ["email", "address"],
|
||||
enabled: true,
|
||||
},
|
||||
invoice_history: {
|
||||
enabled: true,
|
||||
},
|
||||
payment_method_update: {
|
||||
enabled: true,
|
||||
},
|
||||
subscription_cancel: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
// business_profile: {
|
||||
// privacy_policy_url: "https://example.com/privacy",
|
||||
// terms_of_service_url: "https://example.com/terms",
|
||||
// },
|
||||
});
|
||||
return configuration;
|
||||
} catch (error: any) {
|
||||
throw new RecaseError({
|
||||
message: `Failed to create billing portal configuration: ${error.message}`,
|
||||
code: ErrCode.StripeError,
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
routeHandler({
|
||||
@@ -37,23 +71,19 @@ export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
|
||||
const stripeCli = createStripeCli({ org, env: req.env });
|
||||
|
||||
// Determine the Stripe customer ID to use
|
||||
let stripeCustomerId: string;
|
||||
|
||||
if (!customer.processor?.id) {
|
||||
let newCus;
|
||||
try {
|
||||
newCus = await createStripeCusIfNotExists({
|
||||
const newCus = await createStripeCusIfNotExists({
|
||||
db: req.db,
|
||||
org,
|
||||
env: req.env,
|
||||
customer,
|
||||
logger: req.logtail,
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw new RecaseError({
|
||||
message: `Failed to create Stripe customer`,
|
||||
code: ErrCode.StripeError,
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
});
|
||||
} finally {
|
||||
|
||||
if (!newCus) {
|
||||
throw new RecaseError({
|
||||
message: `Failed to create Stripe customer`,
|
||||
@@ -62,26 +92,78 @@ export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
});
|
||||
}
|
||||
|
||||
const portal = await stripeCli.billingPortal.sessions.create({
|
||||
customer: newCus.id,
|
||||
return_url: returnUrl || toSuccessUrl({ org, env: req.env }),
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
customer_id: customer.id,
|
||||
url: portal.url,
|
||||
stripeCustomerId = newCus.id;
|
||||
} catch (error: any) {
|
||||
throw new RecaseError({
|
||||
message: `Failed to create Stripe customer`,
|
||||
code: ErrCode.StripeError,
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const portal = await stripeCli.billingPortal.sessions.create({
|
||||
customer: customer.processor.id,
|
||||
stripeCustomerId = customer.processor.id;
|
||||
}
|
||||
|
||||
// Create billing portal session
|
||||
let portal;
|
||||
try {
|
||||
portal = await stripeCli.billingPortal.sessions.create({
|
||||
customer: stripeCustomerId,
|
||||
return_url: returnUrl || toSuccessUrl({ org, env: req.env }),
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log(`Code: ${error.code}, Message: ${error.message}`);
|
||||
|
||||
res.status(200).json({
|
||||
customer_id: customer.id,
|
||||
url: portal.url,
|
||||
});
|
||||
// Check if the error is due to missing default configuration
|
||||
if (
|
||||
error.message &&
|
||||
error.message.includes("default configuration has not been created")
|
||||
) {
|
||||
try {
|
||||
// Create a default billing portal configuration
|
||||
req.logtail?.info(
|
||||
`Creating default billing portal configuration for customer ${customer.id}`
|
||||
);
|
||||
|
||||
const configuration =
|
||||
await createDefaultBillingPortalConfiguration(stripeCli);
|
||||
|
||||
req.logtail?.info(
|
||||
"Successfully created billing portal configuration",
|
||||
{
|
||||
configurationId: configuration.id,
|
||||
orgId: org.id,
|
||||
}
|
||||
);
|
||||
|
||||
// Retry creating the portal session with the new configuration
|
||||
portal = await stripeCli.billingPortal.sessions.create({
|
||||
customer: stripeCustomerId,
|
||||
return_url: returnUrl || toSuccessUrl({ org, env: req.env }),
|
||||
configuration: configuration.id,
|
||||
});
|
||||
} catch (configError: any) {
|
||||
req.logtail?.error(
|
||||
"Failed to create billing portal configuration",
|
||||
{
|
||||
error: configError.message,
|
||||
orgId: org.id,
|
||||
}
|
||||
);
|
||||
throw new RecaseError({
|
||||
message: `Failed to create billing portal configuration: ${configError.message}`,
|
||||
code: ErrCode.StripeError,
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
customer_id: customer.id,
|
||||
url: portal.url,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,12 +3,13 @@ import { z } from "zod";
|
||||
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { CusService } from "../CusService.js";
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { AttachScenario, ErrCode } from "@autumn/shared";
|
||||
import { CusProductService } from "../cusProducts/CusProductService.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
import { handleDecreaseAndTransfer } from "./handleTransferProduct/handleDecreaseAndTransfer.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { deleteCusCache } from "../cusCache/updateCachedCus.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
const TransferProductSchema = z.object({
|
||||
from_entity_id: z.string().nullish(),
|
||||
to_entity_id: z.string(),
|
||||
@@ -119,6 +120,21 @@ export const handleTransferProduct = async (req: any, res: any) =>
|
||||
internal_entity_id: toEntity.internal_id,
|
||||
},
|
||||
});
|
||||
|
||||
await addProductsUpdatedWebhookTask({
|
||||
req,
|
||||
internalCustomerId: customer.internal_id,
|
||||
org: req.org,
|
||||
env: req.env,
|
||||
customerId: customer.id || customer.internal_id,
|
||||
scenario: AttachScenario.New,
|
||||
cusProduct: {
|
||||
...cusProduct,
|
||||
entity_id: toEntity.id,
|
||||
internal_entity_id: toEntity.internal_id,
|
||||
},
|
||||
logger: req.logger,
|
||||
});
|
||||
}
|
||||
|
||||
await deleteCusCache({
|
||||
|
||||
@@ -100,6 +100,6 @@ export const handleDecreaseAndTransfer = async ({
|
||||
},
|
||||
product
|
||||
),
|
||||
sendWebhook: false,
|
||||
scenario: AttachScenario.New,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,10 +13,8 @@ import {
|
||||
} from "@/trigger/updateBalanceTask.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
|
||||
import {
|
||||
getCusEntBalance,
|
||||
getUnlimitedAndUsageAllowed,
|
||||
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
|
||||
import { getUnlimitedAndUsageAllowed } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
|
||||
import { getCusEntBalance } from "@autumn/shared";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
|
||||
|
||||
@@ -9,10 +9,7 @@ import { adjustAllowance } from "@/trigger/adjustAllowance.js";
|
||||
import { CusPriceService } from "@/internal/customers/cusProducts/cusPrices/CusPriceService.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import {
|
||||
getCusEntBalance,
|
||||
getRelatedCusPrice,
|
||||
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
|
||||
import { getCusEntBalance } from "@autumn/shared";
|
||||
import { performDeductionOnCusEnt } from "@/trigger/updateBalanceTask.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
|
||||
@@ -70,16 +70,12 @@ export const sendSvixThresholdReachedEvent = async ({
|
||||
});
|
||||
|
||||
if (fullCus.entity) {
|
||||
const entities = await EntityService.list({
|
||||
db,
|
||||
internalCustomerId: fullCus.internal_id,
|
||||
});
|
||||
fullCus.entities = entities;
|
||||
await getSingleEntityResponse({
|
||||
org,
|
||||
env,
|
||||
features,
|
||||
fullCus,
|
||||
entity: fullCus.entity,
|
||||
entityId: fullCus.entity.id,
|
||||
});
|
||||
}
|
||||
@@ -240,15 +236,11 @@ export const handleThresholdReached = async ({
|
||||
});
|
||||
|
||||
if (fullCus.entity) {
|
||||
const entities = await EntityService.list({
|
||||
db,
|
||||
internalCustomerId: fullCus.internal_id,
|
||||
});
|
||||
fullCus.entities = entities;
|
||||
await getSingleEntityResponse({
|
||||
org,
|
||||
env,
|
||||
features,
|
||||
entity: fullCus.entity,
|
||||
fullCus,
|
||||
entityId: fullCus.entity.id,
|
||||
});
|
||||
|
||||
71
shared/utils/cusEntUtils/balanceUtils.ts
Normal file
71
shared/utils/cusEntUtils/balanceUtils.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { FullCustomerEntitlement } from "../../models/cusProductModels/cusEntModels/cusEntModels.js";
|
||||
import { notNullish, nullish } from "../utils.js";
|
||||
|
||||
export const getSummedEntityBalances = ({
|
||||
cusEnt,
|
||||
}: {
|
||||
cusEnt: FullCustomerEntitlement;
|
||||
}) => {
|
||||
if (nullish(cusEnt.entities)) {
|
||||
return {
|
||||
balance: 0,
|
||||
adjustment: 0,
|
||||
unused: 0,
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
balance: Object.values(cusEnt.entities!).reduce(
|
||||
(acc, curr) => acc + curr.balance,
|
||||
0
|
||||
),
|
||||
adjustment: Object.values(cusEnt.entities!).reduce(
|
||||
(acc, curr) => acc + curr.adjustment,
|
||||
0
|
||||
),
|
||||
unused: 0,
|
||||
count: Object.values(cusEnt.entities!).length,
|
||||
};
|
||||
};
|
||||
|
||||
export const getCusEntBalance = ({
|
||||
cusEnt,
|
||||
entityId,
|
||||
}: {
|
||||
cusEnt: FullCustomerEntitlement;
|
||||
entityId?: string | null;
|
||||
}) => {
|
||||
let entitlement = cusEnt.entitlement;
|
||||
let ent = cusEnt.entitlement;
|
||||
let feature = ent.feature;
|
||||
|
||||
if (notNullish(entitlement.entity_feature_id)) {
|
||||
if (nullish(entityId)) {
|
||||
return getSummedEntityBalances({
|
||||
cusEnt,
|
||||
});
|
||||
} else {
|
||||
let entityBalance = cusEnt.entities?.[entityId!]?.balance;
|
||||
let adjustment = cusEnt.entities?.[entityId!]?.adjustment || 0;
|
||||
|
||||
if (nullish(entityBalance)) {
|
||||
return { balance: 0, adjustment: 0, unused: 0, count: 1 };
|
||||
}
|
||||
|
||||
return {
|
||||
balance: entityBalance,
|
||||
adjustment,
|
||||
unused: 0,
|
||||
count: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
balance: cusEnt.balance,
|
||||
adjustment: cusEnt.adjustment,
|
||||
unused: cusEnt.replaceables?.length || 0,
|
||||
count: 1,
|
||||
};
|
||||
};
|
||||
@@ -9,6 +9,9 @@ export * from "./cusProductUtils/cusProductConstants.js";
|
||||
export * from "./cusProductUtils/cusProductUtils.js";
|
||||
export * from "./utils.js";
|
||||
|
||||
// Cus ent utils
|
||||
export * from "./cusEntUtils/balanceUtils.js";
|
||||
|
||||
// Item utils
|
||||
export * from "./productV2Utils/productItemUtils/mapToItem.js";
|
||||
export * from "./productV2Utils/productItemUtils/productItemUtils.js";
|
||||
|
||||
@@ -22,6 +22,7 @@ import { getBackendErr, notNullish } from "@/utils/genUtils";
|
||||
import CopyButton from "@/components/general/CopyButton";
|
||||
import { AlertCircle, Info, InfoIcon } from "lucide-react";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
import { getCusEntBalance } from "@autumn/shared";
|
||||
|
||||
function UpdateCusEntitlement({
|
||||
selectedCusEntitlement,
|
||||
@@ -32,17 +33,40 @@ function UpdateCusEntitlement({
|
||||
}) {
|
||||
const { customer, refetch } = useCusQuery();
|
||||
const { entityId } = useCustomerContext();
|
||||
// const { customer, env, cusMutate, entityId } = useCustomerContext();
|
||||
|
||||
const cusEnt = selectedCusEntitlement;
|
||||
|
||||
console.log(
|
||||
"Balance: ",
|
||||
cusEnt
|
||||
? getCusEntBalance({
|
||||
cusEnt: cusEnt!,
|
||||
entityId,
|
||||
}).balance
|
||||
: null
|
||||
);
|
||||
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
console.log(
|
||||
`Cus ent: ${cusEnt?.entitlement.feature_id}, Balances: `,
|
||||
cusEnt
|
||||
? getCusEntBalance({
|
||||
cusEnt: cusEnt!,
|
||||
entityId,
|
||||
})
|
||||
: null
|
||||
);
|
||||
|
||||
const [updateFields, setUpdateFields] = useState<any>({
|
||||
balance:
|
||||
entityId && notNullish(cusEnt?.entities?.[entityId]?.balance)
|
||||
? cusEnt?.entities?.[entityId]?.balance
|
||||
: cusEnt?.balance,
|
||||
balance: cusEnt
|
||||
? getCusEntBalance({
|
||||
cusEnt,
|
||||
entityId,
|
||||
}).balance
|
||||
: null,
|
||||
|
||||
next_reset_at: cusEnt?.next_reset_at,
|
||||
});
|
||||
|
||||
@@ -55,10 +79,12 @@ function UpdateCusEntitlement({
|
||||
|
||||
useEffect(() => {
|
||||
setUpdateFields({
|
||||
balance:
|
||||
entityId && notNullish(cusEnt?.entities?.[entityId]?.balance)
|
||||
? cusEnt?.entities?.[entityId]?.balance
|
||||
: cusEnt?.balance,
|
||||
balance: cusEnt
|
||||
? getCusEntBalance({
|
||||
cusEnt: cusEnt!,
|
||||
entityId,
|
||||
}).balance
|
||||
: null,
|
||||
next_reset_at: cusEnt?.next_reset_at,
|
||||
});
|
||||
}, [selectedCusEntitlement]);
|
||||
|
||||
Reference in New Issue
Block a user