fix: preserve billing response shape
This commit is contained in:
1 parent
600cba023c
commit
2ef3da7af5
7 files changed
+488
-6
No files matched your search
@@ -0,0 +1,172 @@
|
||||
type PayloadRecord = Record<string, unknown>;
|
||||
|
||||
function invalid(context: string, path: string, expected: string): never {
|
||||
const separator = path === "响应" ? "" : " ";
|
||||
throw new Error(`${context}数据格式错误:${path}${separator}必须是${expected}。`);
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, path: string, context: string): PayloadRecord {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
||||
invalid(context, path, "对象");
|
||||
}
|
||||
return value as PayloadRecord;
|
||||
}
|
||||
|
||||
function requireArray(record: PayloadRecord, field: string, context: string, path = field): unknown[] {
|
||||
if (!Array.isArray(record[field])) invalid(context, path, "数组");
|
||||
return record[field];
|
||||
}
|
||||
|
||||
function requireString(record: PayloadRecord, field: string, context: string, path: string): void {
|
||||
if (typeof record[field] !== "string") invalid(context, path, "字符串");
|
||||
}
|
||||
|
||||
function requireOptionalString(record: PayloadRecord, field: string, context: string, path: string): void {
|
||||
if (record[field] !== undefined && record[field] !== null && typeof record[field] !== "string") {
|
||||
invalid(context, path, "字符串");
|
||||
}
|
||||
}
|
||||
|
||||
function requireNumber(record: PayloadRecord, field: string, context: string, path: string): void {
|
||||
if (typeof record[field] !== "number" || !Number.isFinite(record[field])) {
|
||||
invalid(context, path, "有限数字");
|
||||
}
|
||||
}
|
||||
|
||||
function requireBoolean(record: PayloadRecord, field: string, context: string, path: string): void {
|
||||
if (typeof record[field] !== "boolean") invalid(context, path, "布尔值");
|
||||
}
|
||||
|
||||
function requireScalar(value: unknown, context: string, path: string): void {
|
||||
if (!(["string", "number", "boolean"] as string[]).includes(typeof value)) {
|
||||
invalid(context, path, "字符串、数字或布尔值");
|
||||
}
|
||||
if (typeof value === "number" && !Number.isFinite(value)) invalid(context, path, "有限数字");
|
||||
}
|
||||
|
||||
function validateBillingAccount(value: unknown, context: string, path: string): void {
|
||||
const account = requireRecord(value, path, context);
|
||||
for (const field of ["accountName", "bankName", "accountNumber", "contact"]) {
|
||||
requireOptionalString(account, field, context, `${path}.${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateWallet(value: unknown, context: string, path: string): void {
|
||||
const wallet = requireRecord(value, path, context);
|
||||
requireString(wallet, "organizationId", context, `${path}.organizationId`);
|
||||
for (const field of ["balanceFen", "totalRechargedFen", "totalChargedFen"]) {
|
||||
requireNumber(wallet, field, context, `${path}.${field}`);
|
||||
}
|
||||
requireString(wallet, "updatedAt", context, `${path}.updatedAt`);
|
||||
}
|
||||
|
||||
function validateSummary(value: unknown, context: string, path: string): void {
|
||||
const summary = requireRecord(value, path, context);
|
||||
for (const field of ["rechargeFen", "chargedFen", "refundedFen", "netConsumedFen"]) {
|
||||
requireNumber(summary, field, context, `${path}.${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateLedgerEntry(value: unknown, context: string, path: string): void {
|
||||
const entry = requireRecord(value, path, context);
|
||||
requireString(entry, "id", context, `${path}.id`);
|
||||
requireString(entry, "organizationId", context, `${path}.organizationId`);
|
||||
requireOptionalString(entry, "accountId", context, `${path}.accountId`);
|
||||
requireOptionalString(entry, "jobId", context, `${path}.jobId`);
|
||||
requireString(entry, "kind", context, `${path}.kind`);
|
||||
requireNumber(entry, "deltaFen", context, `${path}.deltaFen`);
|
||||
requireNumber(entry, "balanceAfterFen", context, `${path}.balanceAfterFen`);
|
||||
requireString(entry, "description", context, `${path}.description`);
|
||||
requireRecord(entry.metadata, `${path}.metadata`, context);
|
||||
requireString(entry, "createdAt", context, `${path}.createdAt`);
|
||||
if (Number.isNaN(Date.parse(entry.createdAt as string))) invalid(context, `${path}.createdAt`, "有效时间字符串");
|
||||
}
|
||||
|
||||
function validateOrganization(value: unknown, context: string, path: string): void {
|
||||
const organization = requireRecord(value, path, context);
|
||||
requireString(organization, "id", context, `${path}.id`);
|
||||
requireString(organization, "name", context, `${path}.name`);
|
||||
}
|
||||
|
||||
function validateAdminOrganization(value: unknown, context: string, path: string): void {
|
||||
const organization = requireRecord(value, path, context);
|
||||
requireString(organization, "id", context, `${path}.id`);
|
||||
requireString(organization, "name", context, `${path}.name`);
|
||||
requireString(organization, "status", context, `${path}.status`);
|
||||
validateWallet(organization.wallet, context, `${path}.wallet`);
|
||||
}
|
||||
|
||||
function validateAdminMember(value: unknown, context: string, path: string): void {
|
||||
const member = requireRecord(value, path, context);
|
||||
for (const field of ["id", "displayName", "phone", "role", "status"]) {
|
||||
requireString(member, field, context, `${path}.${field}`);
|
||||
}
|
||||
requireOptionalString(member, "organizationId", context, `${path}.organizationId`);
|
||||
}
|
||||
|
||||
function validatePriceRule(value: unknown, context: string, path: string): void {
|
||||
const rule = requireRecord(value, path, context);
|
||||
for (const field of ["id", "provider", "capability", "unit"]) {
|
||||
requireString(rule, field, context, `${path}.${field}`);
|
||||
}
|
||||
requireOptionalString(rule, "reqKey", context, `${path}.reqKey`);
|
||||
requireOptionalString(rule, "variantKey", context, `${path}.variantKey`);
|
||||
requireOptionalString(rule, "note", context, `${path}.note`);
|
||||
requireNumber(rule, "standardUnitPriceFen", context, `${path}.standardUnitPriceFen`);
|
||||
requireNumber(rule, "markupMultiplier", context, `${path}.markupMultiplier`);
|
||||
requireBoolean(rule, "enabled", context, `${path}.enabled`);
|
||||
|
||||
if (rule.conditions !== undefined && rule.conditions !== null) {
|
||||
requireRecord(rule.conditions, `${path}.conditions`, context);
|
||||
}
|
||||
if (rule.source !== undefined && rule.source !== null) {
|
||||
const source = requireRecord(rule.source, `${path}.source`, context);
|
||||
requireOptionalString(source, "url", context, `${path}.source.url`);
|
||||
}
|
||||
if (rule.parameterDimensions !== undefined && rule.parameterDimensions !== null) {
|
||||
if (!Array.isArray(rule.parameterDimensions)) invalid(context, `${path}.parameterDimensions`, "数组");
|
||||
rule.parameterDimensions.forEach((dimension, dimensionIndex) => {
|
||||
const dimensionPath = `${path}.parameterDimensions[${dimensionIndex}]`;
|
||||
const item = requireRecord(dimension, dimensionPath, context);
|
||||
requireString(item, "key", context, `${dimensionPath}.key`);
|
||||
requireOptionalString(item, "label", context, `${dimensionPath}.label`);
|
||||
requireScalar(item.baselineValue, context, `${dimensionPath}.baselineValue`);
|
||||
if (item.defaultValue !== undefined && item.defaultValue !== null) {
|
||||
requireScalar(item.defaultValue, context, `${dimensionPath}.defaultValue`);
|
||||
}
|
||||
requireArray(item, "tiers", context, `${dimensionPath}.tiers`).forEach((tier, tierIndex) => {
|
||||
const tierPath = `${dimensionPath}.tiers[${tierIndex}]`;
|
||||
const tierItem = requireRecord(tier, tierPath, context);
|
||||
requireScalar(tierItem.value, context, `${tierPath}.value`);
|
||||
requireOptionalString(tierItem, "label", context, `${tierPath}.label`);
|
||||
requireNumber(tierItem, "standardFactor", context, `${tierPath}.standardFactor`);
|
||||
requireNumber(tierItem, "markupMultiplier", context, `${tierPath}.markupMultiplier`);
|
||||
requireBoolean(tierItem, "enabled", context, `${tierPath}.enabled`);
|
||||
requireOptionalString(tierItem, "note", context, `${tierPath}.note`);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function parseAdminBillingPayload<T>(payload: T): T {
|
||||
const context = "超管计费";
|
||||
const record = requireRecord(payload, "响应", context);
|
||||
validateBillingAccount(record.billingAccount, context, "billingAccount");
|
||||
requireArray(record, "organizations", context).forEach((item, index) => validateAdminOrganization(item, context, `organizations[${index}]`));
|
||||
requireArray(record, "members", context).forEach((item, index) => validateAdminMember(item, context, `members[${index}]`));
|
||||
requireArray(record, "ledger", context).forEach((item, index) => validateLedgerEntry(item, context, `ledger[${index}]`));
|
||||
requireArray(record, "priceRules", context).forEach((item, index) => validatePriceRule(item, context, `priceRules[${index}]`));
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function parseMemberBillingPayload<T>(payload: T): T {
|
||||
const context = "成员计费";
|
||||
const record = requireRecord(payload, "响应", context);
|
||||
validateOrganization(record.organization, context, "organization");
|
||||
validateBillingAccount(record.billingAccount, context, "billingAccount");
|
||||
validateWallet(record.wallet, context, "wallet");
|
||||
requireArray(record, "ledger", context).forEach((item, index) => validateLedgerEntry(item, context, `ledger[${index}]`));
|
||||
validateSummary(record.summary, context, "summary");
|
||||
validateSummary(record.personal, context, "personal");
|
||||
return payload;
|
||||
}
|
||||
Reference in new issue
Block a user