From ecad9f1de8e3c152f7a26d11bc20c298f4ae3130 Mon Sep 17 00:00:00 2001 From: Vishal Katyal Date: Sat, 8 Aug 2026 17:03:59 -0400 Subject: [PATCH] fix: accept both spec postures for a failed payment at completion Observed: test_payment_failure required HTTP 402 plus the English prose Payment Failed in the response body. The status code 402 appears nowhere in the 2026-04-08 specification, and the prose is the hardcoded mock string of the Flower Shop sample server (Payment Failed: Insufficient Funds (Mock)), not a spec requirement. Expected: payment_failed is a standard error message code (checkout.md error code table; error_code.json), and checkout-rest.md Error Responses prescribes that business outcomes return HTTP 200 with the UCP envelope and a typed messages[] entry. A conformant server that answers completion with HTTP 200 and a payment_failed error message failed this test with: Expected status 402, got 200. The fix routes the assertion through assert_business_error, the same dual posture helper already used by test_out_of_stock and test_structured_error_messages in this file: a 4xx rejection must describe the payment problem in its body, and a 2xx answer must carry a full typed error message with code payment_failed on a checkout that has not completed and carries no order. The relaxation stays sound: a server that neither rejects with a 4xx nor reports payment_failed in band, or that silently completes the checkout, still fails. Verified against both the Python and the Node reference servers (both suites green) and against stub servers implementing the in band posture and two genuinely broken behaviors. --- validation_test.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/validation_test.py b/validation_test.py index baac374..51d9718 100644 --- a/validation_test.py +++ b/validation_test.py @@ -341,7 +341,11 @@ def test_payment_failure(self) -> None: Given a checkout session ready for completion, When a payment instrument with a known failing token ('fail_token') is submitted, - Then the server should return a 402 Payment Required error. + Then the server either rejects with a 4xx describing the payment + problem, or answers in-band per the spec's error model with a typed + 'payment_failed' error message (checkout.md standard error codes / + error_code.json; checkout-rest.md "Error Responses": business + outcomes return HTTP 200 with the UCP envelope and messages[]). """ response_json = self.create_checkout_session(handlers=[]) checkout_id = checkout.Checkout(**response_json).id @@ -358,10 +362,10 @@ def test_payment_failure(self) -> None: headers=integration_test_utils.get_headers(), ) - self.assert_4xx_error( + self.assert_business_error( response, - expected_status=402, - substring="Payment Failed", + accepted_codes={"payment_failed"}, + error_4xx_substring="payment", ) def test_complete_without_fulfillment(self) -> None: