fix: type errors

This commit is contained in:
John Yeo
2025-11-10 10:28:45 +00:00
parent f5e2599128
commit e6e75d77ff
14 changed files with 86 additions and 155 deletions

View File

@@ -3,11 +3,7 @@ import { Feature, ApiCusFeature } from "@autumn/shared";
export interface CheckData {
customerId: string;
entityId?: string;
// apiCustomer: ApiCustomer;
cusFeature?: ApiCusFeature
// cusEnts: FullCusEntWithFullCusProduct[];
originalFeature: Feature;
featureToUse: Feature;
// cusProducts: FullCusProduct[];
// entity?: Entity;
}

View File

@@ -1,93 +1,19 @@
import {
ApiVersion,
type ApiVersionClass,
BillingInterval,
type Feature,
type FreeTrial,
type FullCusProduct,
type FullCustomer,
type FullCustomerEntitlement,
isTrialing,
type ProductItem,
SuccessCode,
UsageModel,
} from "@autumn/shared";
import { Decimal } from "decimal.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { featureToCusPrice } from "@/internal/customers/cusProducts/cusPrices/convertCusPriceUtils.js";
import { getProration } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js";
import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js";
import { isFeaturePriceItem } from "@/internal/products/product-items/productItemUtils/getItemType.js";
import { itemToPriceOrTiers } from "@/internal/products/product-items/productItemUtils.js";
import { notNullish } from "@/utils/genUtils.js";
import { getCheckPreview } from "./getCheckPreview.js";
export const getBooleanEntitledResult = async ({
db,
fullCus,
cusEnts,
res,
feature,
apiVersion,
withPreview,
cusProducts,
allFeatures,
}: {
db: DrizzleCli;
fullCus: FullCustomer;
cusEnts: FullCustomerEntitlement[];
res: any;
feature: Feature;
apiVersion: ApiVersionClass;
withPreview: boolean;
cusProducts: FullCusProduct[];
allFeatures: Feature[];
}) => {
const allowed = cusEnts.some((cusEnt) => {
const featureMatch = cusEnt.internal_feature_id === feature.internal_id;
const entityFeatureId = cusEnt.entitlement.entity_feature_id;
const compareEntity =
notNullish(entityFeatureId) && notNullish(fullCus.entity);
const entityMatch = compareEntity
? entityFeatureId === fullCus.entity!.feature_id
: true;
return featureMatch && entityMatch;
});
if (apiVersion.gte(ApiVersion.V1_1)) {
return res.status(200).json({
customer_id: fullCus.id,
feature_id: feature.id,
code: SuccessCode.FeatureFound,
allowed,
preview: withPreview
? await getCheckPreview({
db,
allowed,
balance: undefined,
feature,
cusProducts,
allFeatures,
})
: undefined,
});
} else {
return res.status(200).json({
allowed,
balances: allowed
? [
{
feature_id: feature.id,
balance: null,
},
]
: [],
});
}
};
export const getOptions = ({
prodItems,
@@ -151,14 +77,15 @@ export const getOptions = ({
);
let currentQuantity = currentOptions?.quantity;
const internalFeatureId = currentOptions?.internal_feature_id;
let prorationAmount = 0;
if (currentQuantity) {
if (currentQuantity && internalFeatureId) {
currentQuantity = currentQuantity * (i.billing_units || 1);
const curPrice = featureToCusPrice({
internalFeatureId: currentOptions?.internal_feature_id!,
cusPrices: cusProduct?.customer_prices!,
internalFeatureId: internalFeatureId,
cusPrices: cusProduct?.customer_prices ?? [],
})?.price;
const curPriceAmount = priceToInvoiceAmount({

View File

@@ -1,5 +1,4 @@
import type { EntityData } from "@autumn/shared";
import type { CustomerData } from "../../../../../../shared/api/common/customerData.js";
import type { CustomerData, EntityData } from "@autumn/shared";
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
import { getCachedApiCustomer } from "../../../customers/cusUtils/apiCusCacheUtils/getCachedApiCustomer.js";
import { getOrCreateApiCustomer } from "../../../customers/cusUtils/getOrCreateApiCustomer.js";

View File

@@ -1,4 +1,6 @@
import {
type FullCusEntWithFullCusProduct,
type FullCustomer,
filterEntityProductCusEnts,
filterOutEntityCusEnts,
filterPerEntityCusEnts,
@@ -6,8 +8,7 @@ import {
sumValues,
} from "@autumn/shared";
import { Decimal } from "decimal.js";
import type { FullCustomer } from "../../../../../../../shared/models/cusModels/fullCusModel.js";
import type { FullCusEntWithFullCusProduct } from "../../../../../../../shared/models/cusProductModels/cusEntModels/cusEntWithProduct.js";
import type { RequestContext } from "../../../../../honoUtils/HonoEnv.js";
export const cusEntsToEntityBreakdown = ({

View File

@@ -77,7 +77,7 @@ export const deleteCusById = async ({
// Delete customer and all entity caches atomically
await deleteCachedApiCustomer({
customerId: customer.id,
customerId: customer.id ?? "",
orgId,
env,
});

View File

@@ -2,21 +2,19 @@ import {
AppEnv,
customers,
ErrCode,
RecaseError,
organizations,
member,
organizations,
RecaseError,
} from "@autumn/shared";
import { and, eq } from "drizzle-orm";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v4";
import type { Context } from "hono";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
import { OrgService } from "@/internal/orgs/OrgService.js";
import {
deleteStripeAccounts,
deleteSvixWebhooks,
deleteStripeWebhooks,
deleteSvixWebhooks,
} from "@/internal/orgs/orgUtils/deleteOrgUtils.js";
import { createRoute } from "../../../../honoMiddlewares/routeHandler.js";
const deleteOrgSchema = z.object({
slug: z.string().min(1, "Organization slug is required"),
@@ -26,9 +24,9 @@ const deleteOrgSchema = z.object({
* DELETE /organizations
* Deletes a platform organization by slug (for test cleanup)
*/
export const handleDeletePlatformOrg = [
zValidator("json", deleteOrgSchema),
async (c: Context<HonoEnv>) => {
export const handleDeletePlatformOrg = createRoute({
body: deleteOrgSchema,
handler: async (c) => {
const ctx = c.get("ctx");
const { db, logger, org: masterOrg } = ctx;
@@ -42,8 +40,6 @@ export const handleDeletePlatformOrg = [
if (!org) {
throw new RecaseError({
message: `Organization with slug "${slug}" not found`,
code: ErrCode.NotFound,
statusCode: 404,
});
}
@@ -93,4 +89,4 @@ export const handleDeletePlatformOrg = [
message: `Organization "${slug}" deleted successfully`,
});
},
];
});

View File

@@ -1,5 +1,5 @@
import type {
type CreateFreeTrial,
CreateFreeTrial,
Entitlement,
Feature,
Price,
@@ -83,7 +83,7 @@ export const convertProductV2ToV1 = ({
is_add_on: productV2.is_add_on,
entitlements: entitlementsRecord,
prices,
free_trial: productV2.free_trial,
group: productV2.group,
free_trial: productV2.free_trial ?? null,
group: productV2.group ?? "",
};
};

View File

@@ -1,5 +1,5 @@
import type { CreateRewardProgram, RewardProgram } from "@autumn/shared";
import { generateId } from "@/utils/genUtils.js";
import { CreateRewardProgram, RewardProgram } from "@autumn/shared";
export const constructRewardProgram = ({
rewardProgramData,
@@ -10,7 +10,7 @@ export const constructRewardProgram = ({
orgId: string;
env: string;
}) => {
let rewardProgram: RewardProgram = {
const rewardProgram: RewardProgram = {
...rewardProgramData,
internal_id: generateId("rs"),
unlimited_redemptions: false,

View File

@@ -17,6 +17,7 @@ import { getCustomerDetails } from "@/internal/customers/cusUtils/getCustomerDet
import { toApiFeature } from "@/internal/features/utils/mapFeatureUtils.js";
import type { AutumnContext } from "../honoUtils/HonoEnv.js";
import type { CheckData } from "../internal/api/check/checkTypes/CheckData.js";
import { getApiCustomerBase } from "../internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.js";
import { generateId } from "../utils/genUtils.js";
export const mergeNewCusEntsIntoCusProducts = ({
@@ -118,35 +119,42 @@ export const handleAllowanceUsed = async ({
}) => {
const { db, org, env, features, logger } = ctx;
// Allowance used...
// Make sure overage allowed is false
const oldCusEnts = structuredClone(cusEnts);
for (const cusEnt of oldCusEnts) {
cusEnt.usage_allowed = false;
const newFullCus = structuredClone(fullCus);
for (const cusProduct of newFullCus.customer_products) {
for (const cusEnt of cusProduct.customer_entitlements) {
cusEnt.usage_allowed = false;
}
}
const clonedNewCusEnts = structuredClone(newCusEnts);
for (const cusEnt of clonedNewCusEnts) {
cusEnt.usage_allowed = false;
}
const { apiCustomer: prevApiCustomer } = await getApiCustomerBase({
ctx,
fullCus: fullCus,
});
const { apiCustomer: newApiCustomer } = await getApiCustomerBase({
ctx,
fullCus: newFullCus,
});
const prevCusFeature = prevApiCustomer.features[feature.id];
const newCusFeature = newApiCustomer.features[feature.id];
const prevCheckData: CheckData = {
fullCus,
cusEnts: oldCusEnts,
customerId: fullCus.id || "",
entityId: fullCus.entity?.id,
cusFeature: prevCusFeature,
originalFeature: feature,
featureToUse: feature,
cusProducts: fullCus.customer_products,
entity: fullCus.entity,
};
const newCheckData: CheckData = {
fullCus,
cusEnts: clonedNewCusEnts,
customerId: newFullCus.id || "",
entityId: newFullCus.entity?.id,
cusFeature: newCusFeature,
originalFeature: feature,
featureToUse: feature,
cusProducts: fullCus.customer_products,
entity: fullCus.entity,
};
const prevCheckResponse = await getV2CheckResponse({
ctx,
checkData: prevCheckData,
@@ -215,13 +223,36 @@ export const handleThresholdReached = async ({
clickhouseClient: null as any,
};
const newFullCus = structuredClone(fullCus);
newFullCus.customer_products = mergeNewCusEntsIntoCusProducts({
cusProducts: fullCus.customer_products,
newCusEnts: newCusEnts,
});
const { apiCustomer: prevApiCustomer } = await getApiCustomerBase({
ctx,
fullCus: fullCus,
});
const { apiCustomer: newApiCustomer } = await getApiCustomerBase({
ctx,
fullCus: newFullCus,
});
const checkData1: CheckData = {
fullCus,
cusEnts,
customerId: fullCus.id || "",
entityId: fullCus.entity?.id,
cusFeature: prevApiCustomer.features[feature.id],
originalFeature: feature,
featureToUse: feature,
};
const checkData2: CheckData = {
customerId: newFullCus.id || "",
entityId: newFullCus.entity?.id,
cusFeature: newApiCustomer.features[feature.id],
originalFeature: feature,
featureToUse: feature,
cusProducts: fullCus.customer_products,
entity: fullCus.entity,
};
const prevCheckResponse = await getV2CheckResponse({
@@ -230,22 +261,6 @@ export const handleThresholdReached = async ({
requiredBalance: 1,
});
const newCusProducts = mergeNewCusEntsIntoCusProducts({
cusProducts: fullCus.customer_products,
newCusEnts: newCusEnts,
});
fullCus.customer_products = newCusProducts;
const checkData2: CheckData = {
fullCus,
cusEnts: newCusEnts,
originalFeature: feature,
featureToUse: feature,
cusProducts: newCusProducts,
entity: fullCus.entity,
};
const newCheckResponse = await getV2CheckResponse({
ctx,
checkData: checkData2,
@@ -263,7 +278,7 @@ export const handleThresholdReached = async ({
env,
features,
logger,
cusProducts: newCusProducts,
cusProducts: newFullCus.customer_products,
expand: [],
apiVersion,
});

View File

@@ -81,6 +81,7 @@ export const initCustomer = async ({
name: customerId,
email: `${customerId}@example.com`,
fingerprint,
metadata: {},
};
const customer = await CusService.get({
@@ -226,6 +227,7 @@ export const initCustomerV2 = async ({
email,
fingerprint: customerData?.fingerprint || undefined,
stripe_id: stripeCus.id,
metadata: {},
});
// 3. Attach payment method

View File

@@ -84,7 +84,7 @@ function createResourceProxy(resource: any, resourceName: string): any {
export function createReadOnlyStripeCli(stripeCli: Stripe): Stripe {
return new Proxy(stripeCli, {
get(target, prop: string) {
const value = target[prop];
const value = target[prop as keyof typeof target];
// If accessing a resource (customers, invoices, etc.)
if (value && typeof value === "object" && !Array.isArray(value)) {

View File

@@ -51,8 +51,7 @@ export const createTestContext = async () => {
// Get org secret key for API calls
// Priority: 1. Environment variable (set by test runner), 2. Org's secret_keys field
const orgSecretKey =
process.env.UNIT_TEST_AUTUMN_SECRET_KEY || org.secret_keys?.[env] || "";
const orgSecretKey = process.env.UNIT_TEST_AUTUMN_SECRET_KEY || "";
if (!orgSecretKey) {
throw new Error(
`No secret key found for org "${orgSlug}" in environment "${env}". ` +

View File

@@ -71,13 +71,9 @@ export const CreateCustomerParamsSchema = z.object({
example: "fp_123abc",
}),
metadata: z
.record(z.string(), z.any())
.default({})
.meta({
description: "Additional metadata for the customer",
example: { company: "Acme Inc" },
}),
metadata: z.record(z.string(), z.any()).default({}).meta({
description: "Additional metadata for the customer",
}),
stripe_id: z.string().optional().meta({
description: "Stripe customer ID if you already have one",

View File

@@ -24,21 +24,21 @@ export const RewardProgram = z.object({
export const CreateRewardProgram = z.object({
id: z.string(),
when: z.nativeEnum(RewardTriggerEvent),
when: z.enum(RewardTriggerEvent),
product_ids: z.array(z.string()).optional(),
exclude_trial: z.boolean().optional(),
internal_reward_id: z.string(),
max_redemptions: z.number().optional(),
received_by: z.enum(["referrer", "all"]),
received_by: z.enum(RewardReceivedBy),
});
export const UpdateRewardProgram = z.object({
when: z.nativeEnum(RewardTriggerEvent),
when: z.enum(RewardTriggerEvent),
product_ids: z.array(z.string()).optional(),
exclude_trial: z.boolean().optional(),
internal_reward_id: z.string(),
max_redemptions: z.number().optional(),
received_by: z.nativeEnum(RewardReceivedBy),
received_by: z.enum(RewardReceivedBy),
});
export type RewardProgram = z.infer<typeof RewardProgram>;