Skip to content

Commit cbf87e2

Browse files
committed
feat: implement 'requires_escalation' and fix AP2 verification
Why: - To enable conformance testing of the checkout escalation flow according to spec. - To fix AP2 signature verification logic and missing exceptions which caused tests to fail/pass falsely. What: - Updated 'complete_checkout' to check for 'simulation_trigger': 'escalation_required'. - Added '/recover/{checkout_id}' mock endpoint for 'continue_url'. - Fixed 'Ap2VerificationError' definition and usage. - Restored '_verify_ap2_mandate' method.
1 parent 7ae4ca6 commit cbf87e2

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

rest/python/server/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,11 @@ class InvalidRequestError(UcpError):
7676
def __init__(self, message: str):
7777
"""Initialize InvalidRequestError."""
7878
super().__init__(message, code="INVALID_REQUEST", status_code=400)
79+
80+
81+
class Ap2VerificationError(UcpError):
82+
"""Raised when AP2 mandate verification fails."""
83+
84+
def __init__(self, message: str, code: str = "mandate_invalid_signature"):
85+
"""Initialize Ap2VerificationError."""
86+
super().__init__(message, code=code, status_code=400)

rest/python/server/server.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ async def ucp_exception_handler(request: Request, exc: UcpError):
5252
)
5353

5454

55+
@app.get("/recover/{checkout_id}")
56+
async def recover_checkout(checkout_id: str):
57+
"""Mock checkout recovery UI."""
58+
return {
59+
"message": f"Recovering checkout {checkout_id}",
60+
"status": "ui_rendering",
61+
}
62+
63+
5564
# Apply business logic implementation to generated routes
5665
routes.ucp_implementation.apply_implementation(
5766
generated_routes.ucp_routes.router

rest/python/server/services/checkout_service.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import config
4141
import db
4242
from enums import CheckoutStatus
43+
from exceptions import Ap2VerificationError
4344
from exceptions import CheckoutNotModifiableError
4445
from exceptions import IdempotencyConflictError
4546
from exceptions import InvalidRequestError
@@ -97,6 +98,8 @@
9798
from ucp_sdk.models.schemas.shopping.types.line_item_resp import (
9899
LineItemResponse,
99100
)
101+
from ucp_sdk.models.schemas.shopping.types.message import Message
102+
from ucp_sdk.models.schemas.shopping.types.message_error import MessageError
100103
from ucp_sdk.models.schemas.shopping.types.order_confirmation import (
101104
OrderConfirmation,
102105
)
@@ -650,6 +653,28 @@ async def complete_checkout(
650653
checkout = await self._get_and_validate_checkout(checkout_id)
651654
self._ensure_modifiable(checkout, "complete")
652655

656+
# Check for risk signal trigger
657+
if risk_signals.get("simulation_trigger") == "escalation_required":
658+
checkout.status = CheckoutStatus.REQUIRES_ESCALATION
659+
checkout.continue_url = AnyUrl(f"{self.base_url}/recover/{checkout.id}")
660+
msg = MessageError(
661+
type="error",
662+
code="requires_buyer_input",
663+
content="Escalation triggered by risk signal",
664+
severity="requires_buyer_input",
665+
)
666+
checkout.messages = [Message(root=msg)]
667+
668+
response_body = checkout.model_dump(mode="json", by_alias=True)
669+
await db.save_checkout(
670+
self.transactions_session,
671+
checkout.id,
672+
checkout.status,
673+
response_body,
674+
)
675+
await self.transactions_session.commit()
676+
return checkout
677+
653678
# Process Payment
654679
await self._process_payment(payment)
655680

@@ -1248,3 +1273,16 @@ async def _process_payment(self, payment: PaymentCreateRequest) -> None:
12481273
else:
12491274
# Unknown handler
12501275
raise InvalidRequestError(f"Unsupported payment handler: {handler_id}")
1276+
1277+
def _verify_ap2_mandate(self, ap2: Ap2CompleteRequest) -> None:
1278+
"""Verify the AP2 mandate.
1279+
1280+
In this sample implementation, we simulate verification failure if the
1281+
mandate contains a specific trigger string.
1282+
"""
1283+
mandate_str = ap2.checkout_mandate.root
1284+
if "invalid_signature" in mandate_str:
1285+
raise Ap2VerificationError(
1286+
"Invalid AP2 mandate signature (mock)",
1287+
code="mandate_invalid_signature",
1288+
)

0 commit comments

Comments
 (0)