Skip to content

fix(rest/python): determine currency server side instead of reading it from the create request - #156

Open
vishkaty wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix/checkout-create-omits-currency
Open

fix(rest/python): determine currency server side instead of reading it from the create request#156
vishkaty wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix/checkout-create-omits-currency

Conversation

@vishkaty

Copy link
Copy Markdown
Contributor

What I ran into

A checkout create that follows the schema returns 500 from the Python sample
server. The same request succeeds once a field the schema says to omit is added,
which is what kept this off the radar.

Observed against main (2e5b77c)

Fresh clone, documented setup, seeded flower shop database:

POST /checkout-sessions
Content-Type: application/json
UCP-Agent: profile="https://example.com/agent"

{"line_items": [{"item": {"id": "bouquet_roses"}, "quantity": 1}]}

HTTP 500  Internal Server Error

Server log:

File "services/checkout_service.py", line 310, in create_checkout
    currency=checkout_req.currency,
AttributeError: 'UnifiedCheckoutCreateRequest' object has no attribute 'currency'

Adding "currency": "USD" to the same body returns 201.

Why I think the minimal body is the correct one

source/schemas/shopping/checkout.json annotates currency with
ucp_request: omit, and describes it as the merchant market determination,
derived from address, context and geo IP. CheckoutCreateRequest in the SDK has
no currency field for that reason, and line_items is its only required
member. So a platform following the schema sends exactly the body above, and
that is the body that fails.

create_checkout reads checkout_req.currency anyway. Because the model sets
extra="allow", that attribute only resolves when a client sends a field the
schema told it to omit, and raises AttributeError otherwise.

Scope

I swept for other reads of request fields marked omit and found one more, the
same pattern on the update path:

Site Status
checkout_service.py:310 create, currency=checkout_req.currency fixed, server determines it
checkout_service.py:432 update, if checkout_req.currency: fixed, update no longer takes currency from the request
checkout_service.py:198 create, model_dump(exclude={... "currency" ...}) already correct, currency is excluded there
rest/nodejs/src/api/checkout.ts:553 already correct, sets currency server side

The Node sample already determines currency itself, so this brings the Python
sample in line with it rather than introducing a new convention.

On the update path I removed the client driven override rather than making the
read tolerant, because the spec assigns the determination to the business. If
you would rather keep accepting a platform supplied currency there, I am happy
to switch that hunk to a tolerant read instead. That is a judgement call and
yours to make.

Why CI did not catch it

Every integration test builds its payload through _create_checkout_payload,
which sets id and currency. Both are ucp_request: omit fields, and
extra="allow" keeps them, so checkout_req.currency always resolved and the
conformant path was never exercised.

Verification

  • uv run --directory rest/python/server pytest: 20 passed, including the two
    new tests
  • Reverted each half of the fix in turn and confirmed the matching test fails,
    so the tests are load bearing rather than incidental
  • Full workflow path from python-server-tests.yml: database init, server boot,
    and simple_happy_path_client.py end to end, exit 0 with no 500 in the log
  • Pinned pre-commit run clean, ruff and ruff format both

Please let me know if the framing or the update path call looks wrong to you and
I will adjust.

…t from the create request

A checkout create that follows the schema returns 500. checkout.json annotates
`currency` with `ucp_request: omit`, so a conformant platform does not send it,
and the generated CheckoutCreateRequest has no such field. create_checkout reads
`checkout_req.currency` regardless, which raises AttributeError on the pydantic
extra lookup and surfaces as Internal Server Error.

Reproduction against main, with the minimal conformant body:

    POST /checkout-sessions
    {"line_items": [{"item": {"id": "bouquet_roses"}, "quantity": 1}]}
    -> 500

The same request with `"currency": "USD"` added returns 201, because
extra="allow" makes the attribute resolve.

The update path had the same read at checkout_service.py:432 and failed the
same way. Since the spec assigns the determination to the business, an update
no longer takes currency from the request either.

The Node sample already sets currency server side, so this brings the Python
sample in line with it.

Adds two integration tests covering create and update with the conformant body.
The existing tests all build payloads through _create_checkout_payload, which
sets id and currency, so this path was never exercised.
@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:needs-triage Signal that the PR is ready for human triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants