diff --git a/apps/api/tests/contract/test_billing_contract.py b/apps/api/tests/contract/test_billing_contract.py index 6f5eb2be6..eff04a304 100644 --- a/apps/api/tests/contract/test_billing_contract.py +++ b/apps/api/tests/contract/test_billing_contract.py @@ -11,8 +11,10 @@ from httpx import AsyncClient from pytest import MonkeyPatch -from tests.support.contract_database import ContractDatabase +from shared.core.billing import BillingCalculator +from shared.core.config.billing import BillingConfig from shared.utils.api_keys import hash_api_key +from tests.support.contract_database import ContractDatabase def _utc_now() -> datetime: @@ -57,6 +59,28 @@ async def _insert_api_key_for_user(user_id: str, api_key: str) -> None: ) +def test_billing_config_should_default_to_one_dollar_fifty_per_hundred_pages( + monkeypatch: MonkeyPatch, +) -> None: + monkeypatch.delenv("MICRO_DOLLARS_PER_PAGE", raising=False) + + config: BillingConfig = BillingConfig(_env_file=None) + + assert config.MICRO_DOLLARS_PER_PAGE == 15_000 + + +def test_billing_calculator_should_charge_one_dollar_fifty_per_hundred_pages() -> None: + calculator: BillingCalculator = BillingCalculator(price_per_page_micros=15_000) + charge = calculator.calculate_page_cost(100) + + assert charge.amount == 1_500_000 + assert charge.to_credit() == 1.5 + assert ( + calculator.format_description(100, "contract.pdf") + == "Document processing: contract.pdf (100 pages @ $0.0150/page)" + ) + + @pytest.mark.asyncio async def test_should_return_the_authenticated_users_initialized_credits_balance( developer_api_client_factory: Callable[ diff --git a/packages/shared-python/shared/core/billing/calculator.py b/packages/shared-python/shared/core/billing/calculator.py index e8bf26716..8ee911818 100644 --- a/packages/shared-python/shared/core/billing/calculator.py +++ b/packages/shared-python/shared/core/billing/calculator.py @@ -3,7 +3,7 @@ Per-page model: Each page costs a fixed amount in micro-credits. $1.00 = 1,000,000 micro-credits -Default: $0.0015/page = 1,500 micro-credits/page +Default: $0.015/page = 15,000 micro-credits/page """ from shared.core.config import settings @@ -21,7 +21,7 @@ class BillingCalculator: Example: calc = BillingCalculator() cost = calc.calculate_page_cost(10) # Returns MicroDollar - print(cost.amount) # 15,000 micro-credits + print(cost.amount) # 150,000 micro-credits """ def __init__(self, price_per_page_micros: int | None = None): @@ -50,7 +50,7 @@ def calculate_page_cost(self, page_count: int) -> MicroDollar: MicroDollar representing total cost Example: - calc.calculate_page_cost(10) # -> MicroDollar(15000) + calc.calculate_page_cost(10) # -> MicroDollar(150000) """ return MicroDollar(page_count * self._price_per_page) @@ -66,7 +66,7 @@ def format_description(self, page_count: int, filename: str | None = None) -> st Human-readable description string Example: - "Document processing: report.pdf (10 pages @ $0.0015/page)" + "Document processing: report.pdf (10 pages @ $0.0150/page)" """ price_dollars = self._price_per_page / 1_000_000 file_name = filename or "file" diff --git a/packages/shared-python/shared/core/billing/micro_dollar.py b/packages/shared-python/shared/core/billing/micro_dollar.py index db02c0aec..6b488a8ee 100644 --- a/packages/shared-python/shared/core/billing/micro_dollar.py +++ b/packages/shared-python/shared/core/billing/micro_dollar.py @@ -2,14 +2,14 @@ MicroDollar value object for type-safe integer-based currency operations. Standard: $1.00 = 1,000,000 micro-credits -This allows representing prices like $0.0015 as 1,500 integers. +This allows representing prices like $0.015 as 15,000 integers. Usage: # Create from int (e.g., from database value) balance = MicroDollar(user.credits_balance) # Calculations - cost = MicroDollar(1500) * 10 # 10 pages @ $0.0015/page + cost = MicroDollar(15000) * 10 # 10 pages @ $0.015/page # Get raw int value (e.g., for database storage) user.credits_balance = cost.amount @@ -26,8 +26,8 @@ class MicroDollar: All arithmetic is integer-based to prevent floating-point errors in billing. Example: - price = MicroDollar(1_500) # $0.0015 - total = price * 10 # 10 pages -> 15,000 micros ($0.015) + price = MicroDollar(15_000) # $0.015 + total = price * 10 # 10 pages -> 150,000 micros ($0.15) """ SCALE = 1_000_000 # $1.00 = 1,000,000 micros @@ -99,7 +99,7 @@ def __mul__(self, quantity: int) -> "MicroDollar": """ Multiply by a scalar quantity (integer only). - Example: 10 pages * $0.0015/page = $0.015 + Example: 10 pages * $0.015/page = $0.15 """ if not isinstance(quantity, int): raise TypeError( diff --git a/packages/shared-python/shared/core/config/billing.py b/packages/shared-python/shared/core/config/billing.py index 7aa818cdd..8860e5a25 100644 --- a/packages/shared-python/shared/core/config/billing.py +++ b/packages/shared-python/shared/core/config/billing.py @@ -27,7 +27,7 @@ class BillingConfig(BaseSettings): # Credits (Micro-Dollar System: $1.00 = 1,000,000 micro-credits) MICRO_DOLLARS_PER_PAGE: int = Field( - default=1500, description="Micro dollars per page ($0.0015 = 1500 micros)" + default=15000, description="Micro dollars per page ($0.015 = 15000 micros)" ) CREDITS_VALID_DAYS: int = Field( default=365, description="Credit validity period in days"