Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class MalformedCredentialException extends PaymentException {
public MalformedCredentialException(String reason) {
super(reason != null ? "Credential is malformed: " + reason + "." : "Credential is malformed.",
402, BASE_URI + "/malformed-credential", "Malformed Credential");
402, BASE_URI + "/malformed-credential", "Malformed Credential", MALFORMED_CREDENTIAL_HINT);
}

public MalformedCredentialException() {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/stripe/mpp/error/PaymentException.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,28 @@

public class PaymentException extends RuntimeException {
public static final String BASE_URI = "https://paymentauth.org/problems";
public static final String PAYMENT_REQUIRED_HINT =
"Use a supported wallet to pay for this resource using one of the supported "
+ "payment methods returned in the WWW-Authenticate header. See https://mpp.dev/tools/wallet.md";
public static final String MALFORMED_CREDENTIAL_HINT =
"Use a supported wallet to construct valid credentials for one of the supported "
+ "payment methods returned in the WWW-Authenticate header. See https://mpp.dev/tools/wallet.md";

private final int httpStatus;
private final String type;
private final String title;
private final String hint;

public PaymentException(String message, int httpStatus, String type, String title) {
this(message, httpStatus, type, title, null);
}

public PaymentException(String message, int httpStatus, String type, String title, String hint) {
super(message);
this.httpStatus = httpStatus;
this.type = type;
this.title = title;
this.hint = hint;
}

public PaymentException(String message) {
Expand All @@ -24,6 +36,7 @@ public PaymentException(String message) {
public int getHttpStatus() { return httpStatus; }
public String getType() { return type; }
public String getTitle() { return title; }
public String getHint() { return hint; }

public Map<String, Object> toProblemDetails() {
return toProblemDetails(null);
Expand All @@ -35,6 +48,9 @@ public Map<String, Object> toProblemDetails(String challengeId) {
details.put("title", title);
details.put("status", httpStatus);
details.put("detail", getMessage());
if (hint != null) {
details.put("hint", hint);
}
if (challengeId != null) {
details.put("challengeId", challengeId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class PaymentMethodUnsupportedException extends PaymentException {
public PaymentMethodUnsupportedException(String method) {
super(method != null ? "Payment method \"" + method + "\" is not supported." : "Payment method is not supported.",
400, BASE_URI + "/method-unsupported", "Method Unsupported");
400, BASE_URI + "/method-unsupported", "Method Unsupported", PAYMENT_REQUIRED_HINT);
}

public PaymentMethodUnsupportedException() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class PaymentRequiredException extends PaymentException {
public PaymentRequiredException(String realm, String description) {
super(buildMessage(realm, description), 402,
BASE_URI + "/payment-required", "Payment Required");
BASE_URI + "/payment-required", "Payment Required", PAYMENT_REQUIRED_HINT);
}

public PaymentRequiredException() {
Expand Down
50 changes: 50 additions & 0 deletions src/test/java/com/stripe/mpp/error/PaymentExceptionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.stripe.mpp.error;

import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class PaymentExceptionTest {
@Test
void paymentRequiredProblemDetailsIncludeHint() {
PaymentException error = new PaymentRequiredException("api.example.com", "Paid endpoint");

Map<String, Object> details = error.toProblemDetails("ch_123");

assertThat(error.getHint()).isEqualTo(PaymentException.PAYMENT_REQUIRED_HINT);
assertThat(details).containsEntry("hint", PaymentException.PAYMENT_REQUIRED_HINT);
assertThat(details).containsEntry("challengeId", "ch_123");
}

@Test
void malformedCredentialProblemDetailsIncludeHint() {
PaymentException error = new MalformedCredentialException("bad base64");

Map<String, Object> details = error.toProblemDetails();

assertThat(error.getHint()).isEqualTo(PaymentException.MALFORMED_CREDENTIAL_HINT);
assertThat(details).containsEntry("hint", PaymentException.MALFORMED_CREDENTIAL_HINT);
}

@Test
void methodUnsupportedProblemDetailsIncludePaymentRequiredHint() {
PaymentException error = new PaymentMethodUnsupportedException("unknown");

Map<String, Object> details = error.toProblemDetails();

assertThat(error.getHint()).isEqualTo(PaymentException.PAYMENT_REQUIRED_HINT);
assertThat(details).containsEntry("hint", PaymentException.PAYMENT_REQUIRED_HINT);
}

@Test
void problemDetailsOmitHintWhenUnset() {
PaymentException error = new InvalidChallengeException("ch_123", "expired");

Map<String, Object> details = error.toProblemDetails();

assertThat(error.getHint()).isNull();
assertThat(details).doesNotContainKey("hint");
}
}