import { DomainConflictError } from '../../../common/errors/domain-conflict.error';
type CommercialInput = Record<string, any>;
function object(input: unknown, code: string): asserts input is CommercialInput {
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new DomainConflictError(code);
}
function text(value: unknown, max: number, code: string): asserts value is string {
if (typeof value !== 'string' || value.trim().length === 0 || value.length > max) throw new DomainConflictError(code);
}
function optionalText(value: unknown, max: number, code: string): void {
if (value !== undefined && value !== null) text(value, max, code);
}
function org(value: unknown, code: string): void { text(value, 200, code); }
function url(value: unknown, code: string): void {
text(value, 2000, code);
try {
const parsed = new URL(value);
if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error('protocol');
} catch {
throw new DomainConflictError(code);
}
}
function datetime(value: unknown, code: string): void {
if (value === undefined || value === null) return;
if (typeof value !== 'string' || value.length > 100 || Number.isNaN(Date.parse(value))) throw new DomainConflictError(code);
}
function safeMinor(value: unknown, code: string): void {
if ((!Number.isSafeInteger(value) || Number(value) < 0) && !(typeof value === 'string' && /^\d+$/.test(value) && value.length <= 20)) throw new DomainConflictError(code);
}
export function assertCommercialPlanInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
text(input.code, 100, code);
text(input.name, 250, code);
optionalText(input.description, 4000, code);
}
export function assertCommercialPlanVersionInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
text(input.planId, 200, code);
text(input.currencyCode, 3, code);
if (!/^[A-Z]{3}$/.test(input.currencyCode)) throw new DomainConflictError(code);
if (!['month', 'year'].includes(String(input.billingInterval))) throw new DomainConflictError(code);
safeMinor(input.basePriceMinor, code);
optionalText(input.providerPriceId, 500, code);
if (!Array.isArray(input.entitlements) || input.entitlements.length > 500) throw new DomainConflictError(code);
for (const entitlement of input.entitlements) {
object(entitlement, code);
text(entitlement.featureKey, 160, code);
if (!['boolean', 'numeric_limit', 'string'].includes(String(entitlement.entitlementType))) throw new DomainConflictError(code);
if (entitlement.booleanValue !== undefined && entitlement.booleanValue !== null && typeof entitlement.booleanValue !== 'boolean') throw new DomainConflictError(code);
if (entitlement.numericValue !== undefined && entitlement.numericValue !== null) safeMinor(entitlement.numericValue, code);
optionalText(entitlement.stringValue, 500, code);
}
}
export function assertCommercialOverrideInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
org(input.organizationId, code);
text(input.featureKey, 160, code);
if (!['boolean', 'numeric_limit', 'string'].includes(String(input.entitlementType))) throw new DomainConflictError(code);
if (input.booleanValue !== undefined && input.booleanValue !== null && typeof input.booleanValue !== 'boolean') throw new DomainConflictError(code);
if (input.numericValue !== undefined && input.numericValue !== null) safeMinor(input.numericValue, code);
optionalText(input.stringValue, 500, code);
text(input.reason, 1000, code);
datetime(input.startsAt, code);
datetime(input.endsAt, code);
if (input.startsAt && input.endsAt && Date.parse(input.startsAt) > Date.parse(input.endsAt)) throw new DomainConflictError(code);
optionalText(input.userId, 200, code);
}
export function assertCommercialRevokeInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
org(input.organizationId, code);
text(input.overrideId, 200, code);
}
export function assertCommercialOrganizationId(value: unknown): asserts value is string {
org(value, 'commercial.input_invalid');
}
export function assertCommercialCheckoutInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
org(input.organizationId, code);
text(input.planVersionId, 200, code);
url(input.successUrl, code);
url(input.cancelUrl, code);
optionalText(input.billingEmail, 320, code);
if (input.billingEmail !== undefined && input.billingEmail !== null && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.billingEmail)) throw new DomainConflictError(code);
optionalText(input.companyName, 300, code);
optionalText(input.userId, 200, code);
}
export function assertCommercialPortalInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
org(input.organizationId, code);
url(input.returnUrl, code);
}
export function assertCommercialPlanChangeInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
org(input.organizationId, code);
text(input.toPlanVersionId, 200, code);
optionalText(input.userId, 200, code);
}
export function assertCommercialCancelInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.input_invalid';
object(input, code);
org(input.organizationId, code);
if (typeof input.atPeriodEnd !== 'boolean') throw new DomainConflictError(code);
optionalText(input.userId, 200, code);
}
export function assertBillingWebhookInput(input: unknown): asserts input is CommercialInput {
const code = 'commercial.webhook_input_invalid';
object(input, code);
text(input.provider, 100, code);
text(input.billingAccountId, 200, code);
if (!Buffer.isBuffer(input.rawBody) || input.rawBody.length === 0 || input.rawBody.length > 2_000_000) throw new DomainConflictError(code);
if (!input.headers || typeof input.headers !== 'object' || Array.isArray(input.headers)) throw new DomainConflictError(code);
for (const [key, value] of Object.entries(input.headers)) {
text(key, 120, code);
if (value !== undefined) text(value, 2000, code);
}
}
export function assertBillingBatchLimit(value: unknown): asserts value is number {
if (!Number.isSafeInteger(value) || Number(value) < 1 || Number(value) > 1000) throw new DomainConflictError('commercial.input_invalid');
}
