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(