fix: unified billing error handling + add billing tests

unified.py: _create_customer now raises CommerceError instead of
returning fake local_ customer IDs that mask failures.
create_subscription raises CommerceError instead of bare Exception.
report_usage raises CommerceError so callers know when usage wasn't
reported.

Added 12 new tests: config AliasChoices regression (P0), commerce
client error paths, unified client error propagation, and webhook
handler edge cases. All 56 billing tests pass.
This commit is contained in:
2026-02-12 10:47:45 -08:00
parent 82c099d128
commit fa2bcb0455
2 changed files with 822 additions and 7 deletions
+17 -7
View File
@@ -19,6 +19,7 @@ import structlog
from pydantic import BaseModel
from bootnode.config import get_settings
from bootnode.core.billing.commerce import CommerceError
from bootnode.core.iam import IAMUser
logger = structlog.get_logger()
@@ -168,12 +169,11 @@ class UnifiedBillingClient:
status=response.status_code,
error=error_data,
)
# Return minimal customer data so billing can still work
return {
"id": f"local_{iam_user.id}",
"hanzo_id": iam_user.id,
"email": iam_user.email,
}
raise CommerceError(
message=error_data.get("message", "Failed to create Commerce customer"),
status_code=response.status_code,
details=error_data,
)
return response.json()
@@ -262,7 +262,11 @@ class UnifiedBillingClient:
if response.status_code >= 400:
error_data = response.json() if response.content else {}
raise Exception(f"Failed to create subscription: {error_data}")
raise CommerceError(
message=error_data.get("message", "Failed to create subscription"),
status_code=response.status_code,
details=error_data,
)
return response.json()
@@ -290,11 +294,17 @@ class UnifiedBillingClient:
)
if response.status_code >= 400:
error_data = response.json() if response.content else {}
logger.error(
"Failed to report usage",
subscription_id=subscription_id,
status=response.status_code,
)
raise CommerceError(
message=error_data.get("message", "Failed to report usage"),
status_code=response.status_code,
details=error_data,
)
async def sync_iam_to_commerce(self, iam_user: IAMUser) -> dict:
"""Sync IAM user data to Commerce customer.