diff --git a/agentscore_commerce/checkout.py b/agentscore_commerce/checkout.py index f8fa17a..10fe9e6 100644 --- a/agentscore_commerce/checkout.py +++ b/agentscore_commerce/checkout.py @@ -149,6 +149,20 @@ def _spec_method_name(spec: CheckoutRailSpec) -> str: return "stripe/spt" # StripeRailSpec is the only remaining variant in CheckoutRailSpec. +def _realm_from_url(url: str) -> str: + """Derive the WWW-Authenticate ``realm`` from the checkout endpoint URL. + + The realm identifies the protection space and, by convention (and to match the Node + SDK, which passes ``new URL(APP_URL).host``), is the bare host, not the full endpoint + URL. ``Checkout(url="https://agents.example.com/purchase")`` yields realm + ``agents.example.com``. Falls back to the input unchanged when it has no parseable host + (e.g. already a bare host, or a relative path). + """ + from urllib.parse import urlparse + + return urlparse(url).netloc or url + + @dataclass class CheckoutRequest: """Framework-neutral HTTP request input to :meth:`Checkout.handle`. @@ -907,7 +921,7 @@ def __init__( getter = lazy_mppx_server( rails=mpp_rails, secret_key=mppx_secret_key, - realm=url, + realm=_realm_from_url(url), ) compose_mppx = make_mppx_compose_hook(server_getter=getter) diff --git a/pyproject.toml b/pyproject.toml index 13d727a..b93dc04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "agentscore-commerce" -version = "2.5.10" +version = "2.5.11" description = "Agent commerce SDK for Python — identity middleware (FastAPI, Flask, Django, AIOHTTP, Sanic, ASGI) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agent commerce." readme = "README.md" license = "MIT" diff --git a/tests/test_checkout.py b/tests/test_checkout.py index 8feccc7..047072e 100644 --- a/tests/test_checkout.py +++ b/tests/test_checkout.py @@ -985,3 +985,15 @@ def test_rails_key_for_mppx_method_returns_none_when_rail_absent() -> None: assert checkout._rails_key_for_mppx_method("solana") is None assert checkout._rails_key_for_mppx_method("stripe") is None assert checkout._rails_key_for_mppx_method("tempo") == "tempo" + + +def test_realm_from_url_derives_bare_host(): + from agentscore_commerce.checkout import _realm_from_url + + # Full endpoint URL -> bare host (matches the Node SDK's new URL(APP_URL).host), + # so the WWW-Authenticate realm is the protection space, not the full path. + assert _realm_from_url("https://agents.agentscore.com/purchase") == "agents.agentscore.com" + assert _realm_from_url("https://agents.example.com:8443/x/y") == "agents.example.com:8443" + # No parseable host: pass through unchanged. + assert _realm_from_url("agents.example.com") == "agents.example.com" + assert _realm_from_url("/purchase") == "/purchase" diff --git a/uv.lock b/uv.lock index 1165035..68590a5 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ constraints = [{ name = "fastapi", specifier = "!=0.136.3" }] [[package]] name = "agentscore-commerce" -version = "2.5.10" +version = "2.5.11" source = { editable = "." } dependencies = [ { name = "agentscore-py" },