/** * MotherShip 结账选价 / FreightProtect 货值校验 */ export type MsCoverageKind = "basic" | "freight_protect"; export type MsCheckoutSelection = { preferredCarrier: string; coverage: MsCoverageKind; cargoValueUsd?: number; }; /** 返回中文错误;null = 通过 */ export function validateMsCheckoutSelection( input: MsCheckoutSelection, ): string | null { const carrier = input.preferredCarrier?.trim(); if (!carrier) return "请选择承运商"; if (carrier.length > 128) return "承运商名称过长"; if (input.coverage !== "basic" && input.coverage !== "freight_protect") { return "保障方案无效"; } if (input.coverage === "freight_protect") { const v = Number(input.cargoValueUsd); if (!Number.isFinite(v) || v <= 0) { return "选择 FreightProtect 时请填写大于 0 的货物价值(美元)"; } if (v > 1_000_000) { return "货物价值超出允许范围"; } } return null; } /** 输入框清洗:允许美元小数(最多两位),并兼容输入过程中的尾随小数点 */ export function sanitizeCargoValueUsdInput(raw: string): string { let cleaned = raw.replace(/[$,\s]/g, "").replace(/[^\d.]/g, ""); const firstDot = cleaned.indexOf("."); if (firstDot >= 0) { cleaned = cleaned.slice(0, firstDot + 1) + cleaned.slice(firstDot + 1).replace(/\./g, ""); } const m = cleaned.match(/^(\d*)(\.?)(\d{0,2})/); if (!m) return ""; return `${m[1] ?? ""}${m[2] ?? ""}${m[3] ?? ""}`; } /** 解析货值(美元);支持小数,四舍五入到分 */ export function parseCargoValueUsd(raw: string): number | null { const cleaned = raw.replace(/[$,\s]/g, "").trim(); if (!cleaned || cleaned === ".") return null; const n = Number(cleaned); if (!Number.isFinite(n) || n <= 0) return null; const cents = Math.round(n * 100) / 100; if (!Number.isFinite(cents) || cents <= 0) return null; return cents; }