import type { IValueObject } from '@gridpilot/shared/domain'; export interface CheckoutPriceProps { amountUsd: number; } export class CheckoutPrice implements IValueObject { private constructor(private readonly amountUsd: number) { if (amountUsd < 0) { throw new Error('Price cannot be negative'); } if (amountUsd > 10000) { throw new Error('Price exceeds maximum of $10,000'); } } static fromString(priceStr: string): CheckoutPrice { const trimmed = priceStr.trim(); if (!trimmed.startsWith('$')) { throw new Error('Invalid price format: missing dollar sign'); } const dollarSignCount = (trimmed.match(/\$/g) || []).length; if (dollarSignCount > 1) { throw new Error('Invalid price format: multiple dollar signs'); } const numericPart = trimmed.substring(1).replace(/,/g, ''); if (numericPart === '') { throw new Error('Invalid price format: no numeric value'); } const amount = parseFloat(numericPart); if (isNaN(amount)) { throw new Error('Invalid price format: not a valid number'); } return new CheckoutPrice(amount); } /** * Factory for a neutral/zero checkout price. * Used when no explicit price can be extracted from the DOM. */ static zero(): CheckoutPrice { return new CheckoutPrice(0); } toDisplayString(): string { return `$${this.amountUsd.toFixed(2)}`; } getAmount(): number { return this.amountUsd; } isZero(): boolean { return this.amountUsd < 0.001; } get props(): CheckoutPriceProps { return { amountUsd: this.amountUsd, }; } equals(other: IValueObject): boolean { return this.props.amountUsd === other.props.amountUsd; } }