Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions rest/python/server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@

_SERVER_VERSION_CACHE = None

# checkout.json annotates `currency` with `ucp_request: omit` and describes
# it as "reflecting the merchant's market determination ... buyers provide
# signals, merchants determine currency". A conformant platform therefore does
# not send it, and the generated CheckoutCreateRequest has no such field. This
# sample serves a single market, so the determination is a constant.
DEFAULT_CURRENCY = "USD"


def get_default_currency() -> str:
"""Return the currency this business trades in."""
return DEFAULT_CURRENCY


def get_server_version() -> str:
"""Read and cache the server version from the discovery profile."""
Expand Down
44 changes: 44 additions & 0 deletions rest/python/server/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,50 @@ def test_profile_includes_cache_control_header(self) -> None:
for forbidden in ("private", "no-store", "no-cache"):
self.assertNotIn(forbidden, directives)

def test_create_omitting_server_determined_fields(self) -> None:
"""A conformant create sends only line_items.

checkout.json marks currency (and id, status, totals, links) with
`ucp_request: omit`, describing currency as derived from address, context
and geo IP because merchants determine it. The generated
CheckoutCreateRequest carries no currency field for that reason, so a
platform following the schema sends neither.

The other tests build their payload with _create_checkout_payload, which
sets id and currency; extra="allow" keeps them, so checkout_req.currency
always resolves and this path is never exercised.
"""
with self.client:
response = self.client.post(
"/checkout-sessions",
headers=self._get_headers(idempotency_key="omit1", request_id="omit1"),
json={"line_items": [{"item": {"id": "rose"}, "quantity": 1}]},
)
self.assertEqual(response.status_code, 201, f"Response: {response.text}")
body = response.json()
self.assertIsInstance(
body.get("currency"),
str,
"server must determine a currency when the platform omits it",
)

def test_update_omitting_server_determined_fields(self) -> None:
"""The update path reads the same omitted field and must not fail."""
with self.client:
created = self.client.post(
"/checkout-sessions",
headers=self._get_headers(idempotency_key="omit2", request_id="omit2"),
json={"line_items": [{"item": {"id": "rose"}, "quantity": 1}]},
)
self.assertEqual(created.status_code, 201, f"Response: {created.text}")
checkout_id = self.get_resource_id(created.json()["id"])
updated = self.client.put(
f"/checkout-sessions/{checkout_id}",
headers=self._get_headers(idempotency_key="omit3", request_id="omit3"),
json={"line_items": [{"item": {"id": "rose"}, "quantity": 2}]},
)
self.assertEqual(updated.status_code, 200, f"Response: {updated.text}")


if __name__ == "__main__":
absltest.main()
8 changes: 5 additions & 3 deletions rest/python/server/services/checkout_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ async def create_checkout(
),
id=checkout_id,
status=CheckoutStatus.IN_PROGRESS,
currency=checkout_req.currency,
currency=config.get_default_currency(),
line_items=line_items,
totals=[
{"type": "subtotal", "amount": 0},
Expand Down Expand Up @@ -429,8 +429,10 @@ async def update_checkout(
)
existing.line_items = line_items

if checkout_req.currency:
existing.currency = checkout_req.currency
# `currency` carries `ucp_request: omit`, so the business determines it
# and an update never takes it from the request. Reading it here also
# raised AttributeError whenever a conformant platform omitted it, which
# is the same defect as the create path.

if checkout_req.payment:
existing.payment = PaymentResponse(
Expand Down
Loading