diff --git a/rest/python/server/generated_routes/ucp_routes.py b/rest/python/server/generated_routes/ucp_routes.py index 06e0d18d..77644bd5 100644 --- a/rest/python/server/generated_routes/ucp_routes.py +++ b/rest/python/server/generated_routes/ucp_routes.py @@ -6,8 +6,13 @@ import ucp_sdk.models.schemas.shopping.checkout import ucp_sdk.models.schemas.shopping.checkout_update_request import ucp_sdk.models.schemas.shopping.order -import ucp_sdk.models.schemas.shopping.payment_create_request -import ucp_sdk.models.schemas.shopping.payment + +try: + import ucp_sdk.models.schemas.shopping.payment_create_request + import ucp_sdk.models.schemas.shopping.payment +except ImportError: + import ucp_sdk.models.schemas.common.types.payment_create_request + import ucp_sdk.models.schemas.common.types.payment router = APIRouter() diff --git a/rest/python/server/integration_test.py b/rest/python/server/integration_test.py index c4f31862..520ea4d7 100644 --- a/rest/python/server/integration_test.py +++ b/rest/python/server/integration_test.py @@ -34,27 +34,29 @@ from ucp_sdk.models.schemas.shopping import ( checkout_create_request as checkout_create_req, ) -from ucp_sdk.models.schemas.shopping import ( - payment_create_request as payment_create_req, -) + +try: + from ucp_sdk.models.schemas.shopping import ( + payment_create_request as payment_create_req, + ) +except ImportError: + from ucp_sdk.models.schemas.common.types import ( + payment_create_request as payment_create_req, + ) + +try: + from ucp_sdk.models.schemas.shopping import ( + payment_complete_request as payment_comp_req, + ) +except ImportError: + from ucp_sdk.models.schemas.common.types import ( + payment_complete_request as payment_comp_req, + ) from ucp_sdk.models.schemas.shopping import ( checkout_complete_request as checkout_comp_req, - payment_complete_request as payment_comp_req, -) -from ucp_sdk.models.schemas.shopping.types import ( - payment_instrument as payment_instr_type, -) -from ucp_sdk.models.schemas.shopping.ap2_mandate import Checkout as Ap2Checkout -from ucp_sdk.models.schemas.shopping.buyer_consent import ( - Checkout as BuyerConsentCheckoutResp, -) -from ucp_sdk.models.schemas.shopping.discount import ( - Checkout as DiscountCheckoutResp, -) -from ucp_sdk.models.schemas.shopping.fulfillment import ( - Checkout as FulfillmentCheckout, ) +from ucp_sdk.models.schemas.shopping.checkout import Checkout as BaseCheckout from ucp_sdk.models.schemas.shopping.order import PlatformSchema from ucp_sdk.models.schemas.shopping.types import ( fulfillment_group_create_request as fulfillment_group_create_req, @@ -75,12 +77,7 @@ FLAGS = flags.FLAGS -class TestCheckout( - BuyerConsentCheckoutResp, - FulfillmentCheckout, - DiscountCheckoutResp, - Ap2Checkout, -): +class TestCheckout(BaseCheckout): """Checkout model supporting Fulfillment, Discount, and AP2 extensions.""" platform: PlatformSchema | None = None @@ -238,7 +235,7 @@ def _create_checkout_payload( # Hierarchical Fulfillment Construction destination = shipping_destination_req.ShippingDestination( - id="dest_1", address_country="US" + id="dest_1", address_country="US", type="shipping_address" ) group = fulfillment_group_create_req.FulfillmentGroupCreateRequest( id="group_1", @@ -272,13 +269,13 @@ def _create_payment_payload(self) -> dict: payload = checkout_comp_req.CheckoutCompleteRequest( payment=payment_comp_req.PaymentCompleteRequest( instruments=[ - payment_instr_type.SelectedPaymentInstrument( - id="instr_1", - handler_id="mock_payment_handler", - type="card", - display={"brand": "Visa", "last_digits": "1234"}, - credential={"type": "token", "token": "success_token"}, - ) + { + "id": "instr_1", + "handler_id": "mock_payment_handler", + "type": "card", + "display": {"brand": "Visa", "last_digits": "1234"}, + "credential": {"type": "token", "token": "success_token"}, + } ] ), risk_signals={}, diff --git a/rest/python/server/models.py b/rest/python/server/models.py index e1197c02..44a2cce7 100644 --- a/rest/python/server/models.py +++ b/rest/python/server/models.py @@ -20,18 +20,9 @@ """ from typing import Any -from ucp_sdk.models.schemas.shopping.ap2_mandate import Checkout as Ap2Checkout -from ucp_sdk.models.schemas.shopping.buyer_consent import ( - Checkout as BuyerConsentCheckoutResp, -) -from ucp_sdk.models.schemas.shopping.discount import ( - Checkout as DiscountCheckoutResp, - DiscountsObject, -) -from ucp_sdk.models.schemas.shopping.fulfillment import ( - Checkout as FulfillmentCheckout, - Fulfillment, -) +from ucp_sdk.models.schemas.shopping.checkout import Checkout as BaseCheckout +from ucp_sdk.models.schemas.shopping.discount import DiscountsObject +from ucp_sdk.models.schemas.shopping.fulfillment import Fulfillment from ucp_sdk.models.schemas.shopping.order import Order from ucp_sdk.models.schemas.shopping.order import PlatformSchema @@ -48,15 +39,14 @@ class UnifiedOrder(Order): """Order model supporting extensions.""" -class UnifiedCheckout( - BuyerConsentCheckoutResp, - FulfillmentCheckout, - DiscountCheckoutResp, - Ap2Checkout, -): +class UnifiedCheckout(BaseCheckout): """Checkout model supporting various extensions.""" platform: PlatformSchema | None = None + fulfillment: Fulfillment | None = None + discounts: DiscountsObject | None = None + buyer_consent: Any | None = None + ap2: Any | None = None class UnifiedCheckoutCreateRequest(CheckoutCreateRequest): diff --git a/rest/python/server/routes/discovery_profile.json b/rest/python/server/routes/discovery_profile.json index dc6232c8..2bceb459 100644 --- a/rest/python/server/routes/discovery_profile.json +++ b/rest/python/server/routes/discovery_profile.json @@ -1,72 +1,72 @@ { "ucp": { - "version": "2026-01-23", + "version": "2026-04-08", "services": { "dev.ucp.shopping": [ { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/overview", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/overview", "transport": "rest", "endpoint": "{{ENDPOINT}}", - "schema": "https://ucp.dev/2026-01-23/services/shopping/openapi.json" + "schema": "https://ucp.dev/2026-04-08/services/shopping/openapi.json" }, { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/overview", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/overview", "transport": "mcp", "endpoint": "{{ENDPOINT}}/mcp", - "schema": "https://ucp.dev/2026-01-23/services/shopping/openrpc.json" + "schema": "https://ucp.dev/2026-04-08/services/shopping/openrpc.json" }, { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/overview", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/overview", "transport": "a2a", "endpoint": "{{ENDPOINT}}/.well-known/agent-card.json" }, { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/overview", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/overview", "transport": "embedded", - "schema": "https://ucp.dev/2026-01-23/services/shopping/embedded.json" + "schema": "https://ucp.dev/2026-04-08/services/shopping/embedded.json" } ] }, "capabilities": { "dev.ucp.shopping.checkout": [ { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/checkout", - "schema": "https://ucp.dev/2026-01-23/schemas/shopping/checkout.json" + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/checkout", + "schema": "https://ucp.dev/2026-04-08/schemas/shopping/checkout.json" } ], "dev.ucp.shopping.order": [ { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/order", - "schema": "https://ucp.dev/2026-01-23/schemas/shopping/order.json" + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/order", + "schema": "https://ucp.dev/2026-04-08/schemas/shopping/order.json" } ], "dev.ucp.shopping.discount": [ { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/discount", - "schema": "https://ucp.dev/2026-01-23/schemas/shopping/discount.json", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/discount", + "schema": "https://ucp.dev/2026-04-08/schemas/shopping/discount.json", "extends": "dev.ucp.shopping.checkout" } ], "dev.ucp.shopping.fulfillment": [ { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/fulfillment", - "schema": "https://ucp.dev/2026-01-23/schemas/shopping/fulfillment.json", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/fulfillment", + "schema": "https://ucp.dev/2026-04-08/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout" } ], "dev.ucp.shopping.buyer_consent": [ { - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/specification/buyer-consent", - "schema": "https://ucp.dev/2026-01-23/schemas/shopping/buyer_consent.json", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/specification/buyer-consent", + "schema": "https://ucp.dev/2026-04-08/schemas/shopping/buyer_consent.json", "extends": "dev.ucp.shopping.checkout" } ] @@ -76,11 +76,11 @@ { "id": "shop_pay", "name": "com.shopify.shop_pay", - "version": "2026-01-23", + "version": "2026-04-08", "spec": "https://shopify.dev/docs/agents/checkout/shop-pay-handler", - "config_schema": "https://shopify.dev/ucp/shop-pay-handler/2026-01-23/config.json", + "config_schema": "https://shopify.dev/ucp/shop-pay-handler/2026-04-08/config.json", "instrument_schemas": [ - "https://shopify.dev/ucp/shop-pay-handler/2026-01-23/instrument.json" + "https://shopify.dev/ucp/shop-pay-handler/2026-04-08/instrument.json" ], "config": { "shop_id": "{{SHOP_ID}}" @@ -91,11 +91,11 @@ { "id": "google_pay", "name": "com.google.pay", - "version": "2026-01-23", - "spec": "https://pay.google.com/gp/p/ucp/2026-01-23/", - "config_schema": "https://pay.google.com/gp/p/ucp/2026-01-23/schemas/config.json", + "version": "2026-04-08", + "spec": "https://pay.google.com/gp/p/ucp/2026-04-08/", + "config_schema": "https://pay.google.com/gp/p/ucp/2026-04-08/schemas/config.json", "instrument_schemas": [ - "https://pay.google.com/gp/p/ucp/2026-01-23/schemas/card_payment_instrument.json" + "https://pay.google.com/gp/p/ucp/2026-04-08/schemas/card_payment_instrument.json" ], "config": { "api_version": 2, @@ -132,8 +132,8 @@ { "id": "mock_payment_handler", "name": "mock_payment_handler", - "version": "2026-01-23", - "spec": "https://ucp.dev/2026-01-23/schemas/mock_payment_handler/spec", + "version": "2026-04-08", + "spec": "https://ucp.dev/2026-04-08/schemas/mock_payment_handler/spec", "config": {} } ] diff --git a/rest/python/server/routes/ucp_implementation.py b/rest/python/server/routes/ucp_implementation.py index 1d2578f2..e810531c 100644 --- a/rest/python/server/routes/ucp_implementation.py +++ b/rest/python/server/routes/ucp_implementation.py @@ -38,9 +38,15 @@ ) from ucp_sdk.models.schemas.shopping.order import Order from ucp_sdk.models.schemas.shopping.order import PlatformSchema -from ucp_sdk.models.schemas.shopping.payment_create_request import ( - PaymentCreateRequest, -) + +try: + from ucp_sdk.models.schemas.shopping.payment_create_request import ( + PaymentCreateRequest, + ) +except ImportError: + from ucp_sdk.models.schemas.common.types.payment_create_request import ( + PaymentCreateRequest, + ) logger = logging.getLogger(__name__) diff --git a/rest/python/server/services/checkout_service.py b/rest/python/server/services/checkout_service.py index 48da4db5..a822b7bc 100644 --- a/rest/python/server/services/checkout_service.py +++ b/rest/python/server/services/checkout_service.py @@ -58,8 +58,6 @@ ResponseCheckoutSchema as ResponseCheckout, ) from ucp_sdk.models.schemas.ucp import ResponseOrderSchema as ResponseOrder -from ucp_sdk.models.schemas.ucp import UcpMetadata -from ucp_sdk.models.schemas.ucp import Version from ucp_sdk.models.schemas.capability import ResponseSchema as Response from ucp_sdk.models.schemas.shopping.checkout_complete_request import ( CheckoutCompleteRequest, @@ -67,20 +65,27 @@ from ucp_sdk.models.schemas.shopping.discount import Allocation from ucp_sdk.models.schemas.shopping.discount import AppliedDiscount from ucp_sdk.models.schemas.shopping.discount import DiscountsObject -from ucp_sdk.models.schemas.shopping.fulfillment import ( - Fulfillment as FulfillmentWrapper, -) from ucp_sdk.models.schemas.shopping.order import ( Fulfillment as OrderFulfillment, ) from ucp_sdk.models.schemas.shopping.order import Order from ucp_sdk.models.schemas.shopping.order import PlatformSchema -from ucp_sdk.models.schemas.shopping.payment_create_request import ( - PaymentCreateRequest, -) -from ucp_sdk.models.schemas.shopping.payment import Payment as PaymentResponse + +try: + from ucp_sdk.models.schemas.shopping.payment_create_request import ( + PaymentCreateRequest, + ) + from ucp_sdk.models.schemas.shopping.payment import ( + Payment as PaymentResponse, + ) +except ImportError: + from ucp_sdk.models.schemas.common.types.payment_create_request import ( + PaymentCreateRequest, + ) + from ucp_sdk.models.schemas.common.types.payment import ( + Payment as PaymentResponse, + ) from ucp_sdk.models.schemas.shopping.types import order_line_item -from ucp_sdk.models.schemas.shopping.types import total as total_resp from ucp_sdk.models.schemas.shopping.types.expectation import Expectation from ucp_sdk.models.schemas.shopping.types.expectation import ( LineItem as ExpectationLineItem, @@ -105,13 +110,23 @@ OrderConfirmation, ) from ucp_sdk.models.schemas.shopping.types.order_line_item import OrderLineItem -from ucp_sdk.models.schemas.shopping.types.postal_address import PostalAddress + +try: + from ucp_sdk.models.schemas.shopping.types.postal_address import PostalAddress +except ImportError: + from ucp_sdk.models.schemas.common.types.postal_address import PostalAddress from ucp_sdk.models.schemas.shopping.types.shipping_destination import ( ShippingDestination as ShippingDestinationResponse, ) -from ucp_sdk.models.schemas.shopping.types.total import ( - Total as TotalResponse, -) + +try: + from ucp_sdk.models.schemas.shopping.types.totals import ( + Total as TotalResponse, + ) +except ImportError: + from ucp_sdk.models.schemas.common.types.totals import ( + Total as TotalResponse, + ) logger = logging.getLogger(__name__) @@ -221,7 +236,7 @@ async def create_checkout( # Initialize fulfillment response fulfillment_resp = None if checkout_req.fulfillment: - req_fulfillment = checkout_req.fulfillment.root + req_fulfillment = checkout_req.fulfillment resp_methods = [] all_li_ids = [li.id for li in line_items] @@ -270,18 +285,25 @@ async def create_checkout( # ShippingDestinationResponse # Extract the inner ShippingDestinationRequest - inner_dest = dest_req.root + inner_dest = getattr(dest_req, "root", dest_req) resp_destinations.append( - FulfillmentDestination( - root=ShippingDestinationResponse( - id=getattr(inner_dest, "id", None) or str(uuid.uuid4()), - address_country=inner_dest.address_country, - postal_code=inner_dest.postal_code, - address_region=inner_dest.address_region, - address_locality=inner_dest.address_locality, - street_address=inner_dest.street_address, - ) + ShippingDestinationResponse( + id=getattr(inner_dest, "id", None) or str(uuid.uuid4()), + type="shipping_address", + address_country=getattr(inner_dest, "address_country", "US"), + postal_code=getattr(inner_dest, "postal_code", "94105"), + address_region=getattr( + inner_dest, + "address_region", + getattr(inner_dest, "region", None), + ), + address_locality=getattr( + inner_dest, + "address_locality", + getattr(inner_dest, "locality", None), + ), + street_address=getattr(inner_dest, "street_address", None), ) ) @@ -291,25 +313,28 @@ async def create_checkout( type=method_type, line_item_ids=method_li_ids, groups=resp_groups or None, - destinations=resp_destinations or None, + destinations=[ + FulfillmentDestination(**d.model_dump()) + for d in resp_destinations + ] + if resp_destinations + else None, selected_destination_id=getattr( method_req, "selected_destination_id", None ), ) ) - fulfillment_resp = FulfillmentWrapper( - root=FulfillmentResponseClass(methods=resp_methods) - ) + fulfillment_resp = FulfillmentResponseClass(methods=resp_methods) checkout = Checkout( ucp=ResponseCheckout( - version=Version(config.get_server_version()), + version=config.get_server_version(), capabilities={ "dev.ucp.shopping.checkout": [ Response( name="dev.ucp.shopping.checkout", - version=Version(config.get_server_version()), + version=config.get_server_version(), ) ] }, @@ -319,7 +344,10 @@ async def create_checkout( status=CheckoutStatus.IN_PROGRESS, currency=checkout_req.currency, line_items=line_items, - totals=[], + totals=[ + TotalResponse(type="subtotal", amount=0), + TotalResponse(type="total", amount=0), + ], links=[], payment=PaymentResponse( instruments=checkout_req.payment.instruments @@ -463,18 +491,16 @@ async def update_checkout( req_fulfillment = checkout_req.fulfillment resp_methods = [] - if req_fulfillment.root.methods: - logging.info( - "Request has %d methods", len(req_fulfillment.root.methods) - ) - for m_req in req_fulfillment.root.methods: + if req_fulfillment.methods: + logging.info("Request has %d methods", len(req_fulfillment.methods)) + for m_req in req_fulfillment.methods: # Find matching existing method to preserve state existing_method = None - if existing.fulfillment and existing.fulfillment.root.methods: + if existing.fulfillment and existing.fulfillment.methods: existing_method = next( ( m - for m in existing.fulfillment.root.methods + for m in existing.fulfillment.methods if m.id == getattr(m_req, "id", None) ), None, @@ -484,9 +510,9 @@ async def update_checkout( if ( not existing_method and not getattr(m_req, "id", None) - and len(existing.fulfillment.root.methods) == 1 + and len(existing.fulfillment.methods) == 1 ): - existing_method = existing.fulfillment.root.methods[0] + existing_method = existing.fulfillment.methods[0] # Resolve ID method_id = getattr(m_req, "id", None) @@ -508,7 +534,7 @@ async def update_checkout( # Use provided destinations for dest_req in m_req.destinations: # Extract inner dest - inner_dest = dest_req.root + inner_dest = dest_req dest_data = inner_dest.model_dump(exclude_none=True) # Persist addresses for known customers @@ -521,10 +547,9 @@ async def update_checkout( ) dest_data["id"] = saved_id + dest_data.setdefault("type", "shipping_address") resp_destinations.append( - FulfillmentDestination( - root=ShippingDestinationResponse(**dest_data) - ) + ShippingDestinationResponse(**dest_data) ) elif existing_method and existing_method.destinations: @@ -533,15 +558,14 @@ async def update_checkout( elif customer_addresses: for addr in customer_addresses: resp_destinations.append( - FulfillmentDestination( - root=ShippingDestinationResponse( - id=addr.id, - street_address=addr.street_address, - city=addr.city, - region=addr.state, # Map state to region - postal_code=addr.postal_code, - address_country=addr.country, - ) + ShippingDestinationResponse( + id=addr.id, + type="shipping_address", + street_address=addr.street_address, + address_locality=addr.city, + address_region=addr.state, # Map state to region + postal_code=addr.postal_code, + address_country=addr.country, ) ) @@ -570,17 +594,22 @@ async def update_checkout( type=method_type, line_item_ids=method_li_ids, groups=resp_groups or None, - destinations=resp_destinations or None, + destinations=[ + d + if isinstance(d, FulfillmentDestination) + else FulfillmentDestination(**d.model_dump()) + for d in resp_destinations + ] + if resp_destinations + else None, selected_destination_id=getattr( m_req, "selected_destination_id", None ), ) resp_methods.append(method_resp) - existing.fulfillment = FulfillmentWrapper( - root=FulfillmentResponseClass( - methods=resp_methods, - ) + existing.fulfillment = FulfillmentResponseClass( + methods=resp_methods, ) if getattr(checkout_req, "discounts", None): @@ -665,8 +694,8 @@ async def complete_checkout( # Validate Fulfillment (Required for completion in this implementation) fulfillment_valid = False - if checkout.fulfillment and checkout.fulfillment.root.methods: - for method in checkout.fulfillment.root.methods: + if checkout.fulfillment and checkout.fulfillment.methods: + for method in checkout.fulfillment.methods: if method.type == "shipping" and not method.selected_destination_id: continue if method.groups: @@ -711,21 +740,18 @@ async def complete_checkout( # Create and persist Order expectations = [] - if checkout.fulfillment and checkout.fulfillment.root.methods: - for method in checkout.fulfillment.root.methods: + if checkout.fulfillment and checkout.fulfillment.methods: + for method in checkout.fulfillment.methods: selected_dest = None if method.selected_destination_id and method.destinations: for dest in method.destinations: - dest_root = getattr(dest, "root", dest) - if ( - getattr(dest_root, "id", None) == method.selected_destination_id - ): + if dest.id == method.selected_destination_id: selected_dest = PostalAddress( - street_address=getattr(dest_root, "street_address", None), - address_locality=getattr(dest_root, "address_locality", None), - address_region=getattr(dest_root, "address_region", None), - postal_code=getattr(dest_root, "postal_code", None), - address_country=getattr(dest_root, "address_country", None), + street_address=getattr(dest, "street_address", None), + address_locality=getattr(dest, "address_locality", None), + address_region=getattr(dest, "address_region", None), + postal_code=getattr(dest, "postal_code", None), + address_country=getattr(dest, "address_country", "US"), ) break @@ -786,25 +812,18 @@ async def complete_checkout( order_line_items.append(oli) order = Order( - ucp=UcpMetadata( - root=ResponseOrder( - version=getattr( - checkout.ucp.root, "version", Version("2026-01-23") - ), - capabilities={ - getattr(k, "root", k): v - for k, v in checkout.ucp.root.capabilities.items() - } - if hasattr(checkout.ucp.root, "capabilities") - and checkout.ucp.root.capabilities - else {}, - ) + ucp=ResponseOrder( + version=getattr(checkout.ucp, "version", "2026-04-08"), + capabilities=dict(checkout.ucp.capabilities) + if hasattr(checkout.ucp, "capabilities") and checkout.ucp.capabilities + else {}, ), id=checkout.order.id, checkout_id=checkout.id, permalink_url=checkout.order.permalink_url, + currency=checkout.currency, line_items=order_line_items, - totals=[total_resp.Total(**t.model_dump()) for t in checkout.totals], + totals=checkout.totals, fulfillment=OrderFulfillment(expectations=expectations, events=[]), ) @@ -1040,25 +1059,25 @@ async def _recalculate_totals( checkout.totals.append(TotalResponse(type="subtotal", amount=grand_total)) # Fulfillment Logic - if checkout.fulfillment and checkout.fulfillment.root.methods: + if checkout.fulfillment and checkout.fulfillment.methods: # Fetch promotions once for the loop promotions = await db.get_active_promotions(self.products_session) - for method in checkout.fulfillment.root.methods: + for method in checkout.fulfillment.methods: # 1. Identify Destination and Calculate Options calculated_options = [] if method.type == "shipping" and method.selected_destination_id: selected_dest = None if method.destinations: for dest in method.destinations: - if dest.root.id == method.selected_destination_id: - selected_dest = dest.root + if dest.id == method.selected_destination_id: + selected_dest = dest break if selected_dest: logger.info( "Calculating options for country: %s (dest_id: %s)", - selected_dest.address_country, + getattr(selected_dest, "address_country", "US"), method.selected_destination_id, ) # Log all available destinations for debugging @@ -1067,7 +1086,7 @@ async def _recalculate_totals( "Available destinations in method %s: %s", method.id, [ - f"{d.root.id} ({d.root.address_country})" + f"{d.id} ({getattr(d, 'address_country', 'US')})" for d in method.destinations ], ) @@ -1075,11 +1094,13 @@ async def _recalculate_totals( # Map ShippingDestination to PostalAddress for service call # Using strong types from SDK address_obj = PostalAddress( - street_address=selected_dest.street_address, - address_locality=selected_dest.address_locality, - address_region=selected_dest.address_region, - postal_code=selected_dest.postal_code, - address_country=selected_dest.address_country, + street_address=getattr(selected_dest, "street_address", None), + address_locality=getattr( + selected_dest, "address_locality", None + ), + address_region=getattr(selected_dest, "address_region", None), + postal_code=getattr(selected_dest, "postal_code", None), + address_country=getattr(selected_dest, "address_country", "US"), ) # Get options from service @@ -1139,7 +1160,11 @@ async def _recalculate_totals( # Multiple groups can have costs. # We assume each group adds to the total. opt_total = next( - (t.amount for t in selected_opt.totals if t.type == "total"), + ( + int(getattr(t.amount, "root", t.amount)) + for t in selected_opt.totals + if t.type == "total" + ), 0, ) grand_total += opt_total @@ -1187,7 +1212,7 @@ async def _recalculate_totals( ) ) checkout.totals.append( - TotalResponse(type="discount", amount=discount_amount) + TotalResponse(type="discount", amount=-discount_amount) ) checkout.totals.append(TotalResponse(type="total", amount=grand_total)) diff --git a/rest/python/server/services/fulfillment_service.py b/rest/python/server/services/fulfillment_service.py index 44c8e154..73456d18 100644 --- a/rest/python/server/services/fulfillment_service.py +++ b/rest/python/server/services/fulfillment_service.py @@ -23,8 +23,19 @@ from ucp_sdk.models.schemas.shopping.types.fulfillment_option import ( FulfillmentOption as FulfillmentOptionResponse, ) -from ucp_sdk.models.schemas.shopping.types.postal_address import PostalAddress -from ucp_sdk.models.schemas.shopping.types.total import Total as TotalResponse + +try: + from ucp_sdk.models.schemas.shopping.types.postal_address import PostalAddress +except ImportError: + from ucp_sdk.models.schemas.common.types.postal_address import PostalAddress +try: + from ucp_sdk.models.schemas.shopping.types.totals import ( + Total as TotalResponse, + ) +except ImportError: + from ucp_sdk.models.schemas.common.types.totals import ( + Total as TotalResponse, + ) class FulfillmentService: