Skip to content

Potential UCP checkout issue: complete endpoint places orders without payment verification #1

Open

Description

@chenshj73

Potential UCP checkout issue: complete endpoint places orders without payment verification

Hi, I noticed a possible payment-flow issue in the current repository state. This is a conservative report based on the current code path, and I may be missing deployment-specific guards outside this repository.

Reviewed HEAD: 7cca048

What I observed

The UCP complete endpoint validates only a PaymentInterface payload, uses it for billing data, and calls Magento order placement without an AP2/UCP credential or settlement check.

Source scan notes:

  • scan_uncovered1000_20260604_batch25: True UCP/AP2-adjacent Magento module. The complete checkout endpoint validates only PaymentInterface, uses it as billing-address data, and RestHandler directly calls guestCartManagement->placeOrder() with no AP2 credential, payment handler, facilitator, settlement, or transfer verification. Response converter also advertises empty payment handlers/instruments.

Relevant code excerpts:

Controller/Service/Shopping/Complete.php:69-88

69	        $paymentData = $this->getAndValidateRequest(
70	            PaymentInterface::class,
71	            $this->paymentFactory->create(...)
72	        );
73	
74	        if ($paymentData instanceof ValidationResult) {
75	            return $this->validationResultToResponse($paymentData);
76	        }
77	
78	        if ($idempotencyResponse = $this->handleIdempotency()) {
79	            return $idempotencyResponse;
80	        }
81	
82	        return $this->errorBoundary(function () use ($checkoutId, $paymentData) {
83	            $completeCheckoutResponse = $this->restHandler->completeCheckout($checkoutId, $paymentData);
84	
85	            if ($completeCheckoutResponse instanceof DataTransferObject) {
86	                $this->idempotencyHandler->storeResponse($this->getHttpRequest(), $completeCheckoutResponse, 201);
87	
88	                return $this->makeJsonResponse($completeCheckoutResponse);

Model/Service/Shopping/RestHandler.php:117-136

117	    public function completeCheckout(string $checkoutId, PaymentInterface $paymentData): FulfillmentCheckoutInterface
118	    {
119	        $cart = $this->getCartByMaskedId($checkoutId);
120	        $payment = $paymentData->getPaymentData();
121	        $billingAddress = $payment->getBillingAddress();
122	
123	        if ($billingAddress) {
124	            $this->checkoutDataProcessor->processBillingAddress($cart, $billingAddress);
125	        }
126	
127	        $this->cartRepository->save($cart);
128	
129	        try {
130	            $orderId = $this->guestCartManagement->placeOrder($checkoutId);
131	        } catch (LocalizedException $e) {
132	            throw new LocalizedException(__('Failed to place order: %1', $e->getMessage()));
133	        }
134	
135	        return $this->quoteToCheckoutResponse->convert($cart, $checkoutId);
136	    }

Model/Service/Shopping/Converter/QuoteToCheckoutResponse.php:115-120

115	        return $this->ucpResponseFactory->create([
116	            'data' => [
117	                UcpResponseCheckoutSchemaInterface::KEY_VERSION => UniversalCommerceProtocolInterface::SPEC_VERSION,
118	                UcpResponseCheckoutSchemaInterface::KEY_CAPABILITIES => $service->getCapabilities(),
119	                UcpResponseCheckoutSchemaInterface::KEY_PAYMENT_HANDLERS => [],
120	            ]

Why this may matter

For paid API, agent, MCP, x402, AP2/UCP, or subscription-gated flows, the payment proof needs to dominate the protected release path. If verification is only structural, settlement is best-effort after release, payment fields are not rebound to server requirements, or the protected resource is reachable outside the paid route, a caller may receive the paid result without a completed payment for the intended resource, amount, asset, or recipient.

Suggested check

Consider making the paid-resource release depend on a payment state that is both verified and settled, or otherwise cryptographically bound to the current server-side requirements (resource, amount, asset, payTo, payer, nonce/idempotency key, and route/session). For intentionally asynchronous settlement, it may be worth failing closed on settlement errors or recording a durable pending state with explicit recovery/reconciliation semantics before returning the protected result.

Conservative caveat

This report is based on the repository code at the reviewed HEAD. If production uses an external gateway, webhook, facilitator, deployment setting, or middleware not present here that enforces the missing binding/settlement step before release, the practical impact may be lower.

Activity

  1. KristofersOzolinsMagebit commented on Aug 11, 2026

    @KristofersOzolinsMagebit
    Member

    Sorry for the late reply. Thank you for reporting this. We're currently working on updating the module to the latest spec version and also doing first stable release. Will test this out once that's done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions