From 1db1950cbfa7964feb731d8a9028bc7368b6dab8 Mon Sep 17 00:00:00 2001 From: ygd58 Date: Fri, 4 Sep 2026 06:30:48 +0000 Subject: [PATCH] fix(parsing): reject Payment-Receipt with a non-success status parsePaymentReceipt checked that the status field was present but never validated its value, so a receipt with status="failed" (or any other string) parsed successfully into a usable Receipt object. Canonical mppx defines receipt status as the literal value "success" and validates that during deserialization -- this SDK's own Receipt class matches that design (the only constructor that sets status is Receipt.success(...), which hardcodes "success"), but the parser didn't enforce it on the read side, so a malicious or buggy server could return a protocol-invalid error-like receipt that this SDK would still hand back to the caller as if it were a valid successful payment. Add a check right after the existing null check: reject with ParseException if status isn't exactly "success". Adds receiptParseRejectsNonSuccessStatus, following the same pattern as the existing receiptParseRejectsMissingMethod / receiptParseRejectsInvalidMethodId tests in this file -- an otherwise well-formed receipt with status="failed" must throw ParseException rather than parse. As with my other PRs, I could not compile/run this against the real dependencies in my sandbox (no Maven Central access) -- please double-check compilation before merge. Fixes tempoxyz/mpp-tools#208 (AGR-2026-086) --- src/main/java/com/stripe/mpp/Parsing.java | 1 + src/test/java/com/stripe/mpp/ParsingTest.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/com/stripe/mpp/Parsing.java b/src/main/java/com/stripe/mpp/Parsing.java index 9640d26..a60f157 100644 --- a/src/main/java/com/stripe/mpp/Parsing.java +++ b/src/main/java/com/stripe/mpp/Parsing.java @@ -172,6 +172,7 @@ static Receipt parsePaymentReceipt(String header) { String status = str(map, "status"); if (status == null) throw new ParseException("Missing status"); + if (!"success".equals(status)) throw new ParseException("Invalid receipt status: " + status); String timestampStr = str(map, "timestamp"); if (timestampStr == null) throw new ParseException("Missing timestamp"); diff --git a/src/test/java/com/stripe/mpp/ParsingTest.java b/src/test/java/com/stripe/mpp/ParsingTest.java index a22f8aa..447da09 100644 --- a/src/test/java/com/stripe/mpp/ParsingTest.java +++ b/src/test/java/com/stripe/mpp/ParsingTest.java @@ -354,6 +354,23 @@ void receiptParseRejectsMissingMethod() { .hasMessageContaining("method"); } + @Test + void receiptParseRejectsNonSuccessStatus() { + // Regression test for AGR-2026-086: an otherwise well-formed receipt + // whose status is anything other than "success" (e.g. "failed", as a + // malicious or buggy server might send) must be rejected, not parsed + // into a usable Receipt. status presence alone was previously + // sufficient; canonical mppx requires the literal value "success". + String header = ChallengeId.b64urlEncode( + "{\"method\":\"tempo\",\"reference\":\"ref-123\",\"status\":\"failed\"," + + "\"timestamp\":\"2025-01-01T12:00:00Z\"}" + ); + + assertThatThrownBy(() -> Receipt.fromPaymentReceipt(header)) + .isInstanceOf(com.stripe.mpp.error.ParseException.class) + .hasMessageContaining("status"); + } + @Test void receiptParseRejectsInvalidMethodId() { String header = ChallengeId.b64urlEncode(