Files
cfw-autumn/server/src/internal/api/check/handleCheck.ts
2026-04-20 13:49:04 +01:00

145 lines
3.6 KiB
TypeScript

import {
AffectedResource,
ApiVersion,
applyResponseVersionChanges,
type CheckParams,
CheckParamsSchema,
CheckQuerySchema,
type CheckResponseV3,
CheckResponseV3Schema,
type ParsedCheckParams,
} from "@autumn/shared";
import { isRetryableDbError } from "@/db/dbUtils.js";
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
import { parseCheckParamsForLock } from "@/internal/balances/utils/lock/parseCheckParamsForLock.js";
import { getCheckData } from "./checkUtils/getCheckData.js";
import { getV2CheckResponse } from "./checkUtils/getV2CheckResponse.js";
import { getCheckPreview } from "./getCheckPreview.js";
import { handleProductCheck } from "./handlers/handleProductCheck.js";
import { runCheckWithTrack } from "./runCheckWithTrack.js";
const DEFAULT_REQUIRED_BALANCE = 1;
export const handleCheck = createRoute({
versionedQuery: {
latest: CheckQuerySchema,
[ApiVersion.V1_2]: CheckQuerySchema,
},
resource: AffectedResource.Check,
body: CheckParamsSchema,
handler: async (c) => {
const rawBody = c.req.valid("json");
const ctx = c.get("ctx");
const body: ParsedCheckParams = parseCheckParamsForLock({
params: rawBody,
});
const {
customer_id,
product_id,
entity_id,
required_quantity,
required_balance,
send_event,
with_preview,
} = body;
// Legacy path - product check
if (product_id) {
const checkProductResult = await handleProductCheck({
ctx: c.get("ctx"),
body: { ...body, product_id }, // Ensure product_id is passed as string
});
return c.json(checkProductResult);
}
const requiredBalance =
required_balance ?? required_quantity ?? DEFAULT_REQUIRED_BALANCE;
let checkData: Awaited<ReturnType<typeof getCheckData>>;
try {
checkData = await getCheckData({
ctx,
body: body as CheckParams & { feature_id: string },
requiredBalance,
});
} catch (error) {
if (!isRetryableDbError({ error })) {
throw error;
}
const fallbackResponse = CheckResponseV3Schema.parse({
allowed: true,
customer_id: customer_id || "",
entity_id: entity_id,
required_balance: requiredBalance,
balance: null,
flag: null,
});
const featureToUse = ctx.features.find(
(feature) => feature.id === body.feature_id,
);
const transformedFallbackResponse = featureToUse
? applyResponseVersionChanges<CheckResponseV3>({
input: fallbackResponse,
targetVersion: ctx.apiVersion,
resource: AffectedResource.Check,
legacyData: {
noCusEnts: false,
featureToUse,
},
ctx,
})
: fallbackResponse;
return c.json(transformedFallbackResponse);
}
let response: CheckResponseV3;
if (send_event || body.lock?.enabled) {
response = await runCheckWithTrack({
ctx,
body,
requiredBalance,
checkData,
});
} else {
response = await getV2CheckResponse({
checkData,
requiredBalance,
});
}
const preview = with_preview
? await getCheckPreview({
ctx,
checkResponse: response,
checkData,
customerId: customer_id,
entityId: entity_id,
})
: undefined;
// Version changes will transform V3 -> V2 -> V1 -> V0 based on target API version
const transformedResponse = applyResponseVersionChanges<CheckResponseV3>({
input: response,
targetVersion: ctx.apiVersion,
resource: AffectedResource.Check,
legacyData: {
noCusEnts:
checkData.apiBalance === undefined && checkData.apiFlag === undefined,
featureToUse: checkData.featureToUse,
},
ctx,
});
return c.json({
...transformedResponse,
preview,
// lock_id: body.lock?.lock_id ?? undefined,
});
},
});