73 lines
1.7 KiB
TypeScript
73 lines
1.7 KiB
TypeScript
import type { IValueObject } from '@gridpilot/shared/domain';
|
|
|
|
export interface CheckoutPriceProps {
|
|
amountUsd: number;
|
|
}
|
|
|
|
export class CheckoutPrice implements IValueObject<CheckoutPriceProps> {
|
|
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<CheckoutPriceProps>): boolean {
|
|
return this.props.amountUsd === other.props.amountUsd;
|
|
}
|
|
} |