Add charge failure details to `/v1/subscription/boost/receipt_credential` 402 response
This commit is contained in:
parent
bc35278684
commit
5990a100db
|
@ -802,9 +802,11 @@ public class SubscriptionController {
|
||||||
public SubscriptionProcessor processor = SubscriptionProcessor.STRIPE;
|
public SubscriptionProcessor processor = SubscriptionProcessor.STRIPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public record CreateBoostReceiptCredentialsResponse(byte[] receiptCredentialResponse) {
|
public record CreateBoostReceiptCredentialsSuccessResponse(byte[] receiptCredentialResponse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record CreateBoostReceiptCredentialsErrorResponse(@JsonInclude(Include.NON_NULL) ChargeFailure chargeFailure) {}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/boost/receipt_credentials")
|
@Path("/boost/receipt_credentials")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ -824,7 +826,8 @@ public class SubscriptionController {
|
||||||
case PROCESSING -> throw new WebApplicationException(Status.NO_CONTENT);
|
case PROCESSING -> throw new WebApplicationException(Status.NO_CONTENT);
|
||||||
case SUCCEEDED -> {
|
case SUCCEEDED -> {
|
||||||
}
|
}
|
||||||
default -> throw new WebApplicationException(Status.PAYMENT_REQUIRED);
|
default -> throw new WebApplicationException(Response.status(Status.PAYMENT_REQUIRED)
|
||||||
|
.entity(new CreateBoostReceiptCredentialsErrorResponse(paymentDetails.chargeFailure())).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
long level = oneTimeDonationConfiguration.boost().level();
|
long level = oneTimeDonationConfiguration.boost().level();
|
||||||
|
@ -875,7 +878,7 @@ public class SubscriptionController {
|
||||||
Tag.of(TYPE_TAG_NAME, "boost"),
|
Tag.of(TYPE_TAG_NAME, "boost"),
|
||||||
UserAgentTagUtil.getPlatformTag(userAgent)))
|
UserAgentTagUtil.getPlatformTag(userAgent)))
|
||||||
.increment();
|
.increment();
|
||||||
return Response.ok(new CreateBoostReceiptCredentialsResponse(receiptCredentialResponse.serialize()))
|
return Response.ok(new CreateBoostReceiptCredentialsSuccessResponse(receiptCredentialResponse.serialize()))
|
||||||
.build();
|
.build();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -117,11 +117,15 @@ public class BraintreeManager implements SubscriptionProcessorManager {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
try {
|
try {
|
||||||
final Transaction transaction = braintreeGateway.transaction().find(paymentId);
|
final Transaction transaction = braintreeGateway.transaction().find(paymentId);
|
||||||
|
ChargeFailure chargeFailure = null;
|
||||||
|
if (!getPaymentStatus(transaction.getStatus()).equals(PaymentStatus.SUCCEEDED)) {
|
||||||
|
chargeFailure = createChargeFailure(transaction);
|
||||||
|
}
|
||||||
return new PaymentDetails(transaction.getGraphQLId(),
|
return new PaymentDetails(transaction.getGraphQLId(),
|
||||||
transaction.getCustomFields(),
|
transaction.getCustomFields(),
|
||||||
getPaymentStatus(transaction.getStatus()),
|
getPaymentStatus(transaction.getStatus()),
|
||||||
transaction.getCreatedAt().toInstant());
|
transaction.getCreatedAt().toInstant(),
|
||||||
|
chargeFailure);
|
||||||
|
|
||||||
} catch (final NotFoundException e) {
|
} catch (final NotFoundException e) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -433,7 +437,7 @@ public class BraintreeManager implements SubscriptionProcessorManager {
|
||||||
|
|
||||||
if (latestTransaction.isPresent()){
|
if (latestTransaction.isPresent()){
|
||||||
paymentProcessing = isPaymentProcessing(latestTransaction.get().getStatus());
|
paymentProcessing = isPaymentProcessing(latestTransaction.get().getStatus());
|
||||||
if (!getPaymentStatus(latestTransaction.get().getStatus()).equals(PaymentStatus.SUCCEEDED)) {
|
if (getPaymentStatus(latestTransaction.get().getStatus()) != PaymentStatus.SUCCEEDED) {
|
||||||
chargeFailure = createChargeFailure(latestTransaction.get());
|
chargeFailure = createChargeFailure(latestTransaction.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,7 +474,10 @@ public class BraintreeManager implements SubscriptionProcessorManager {
|
||||||
|
|
||||||
final String code;
|
final String code;
|
||||||
final String message;
|
final String message;
|
||||||
if (transaction.getProcessorResponseCode() != null) {
|
if (transaction.getStatus() == Transaction.Status.VOIDED) {
|
||||||
|
code = "voided";
|
||||||
|
message = "voided";
|
||||||
|
} else if (transaction.getProcessorResponseCode() != null) {
|
||||||
code = transaction.getProcessorResponseCode();
|
code = transaction.getProcessorResponseCode();
|
||||||
message = transaction.getProcessorResponseText();
|
message = transaction.getProcessorResponseText();
|
||||||
} else if (transaction.getGatewayRejectionReason() != null) {
|
} else if (transaction.getGatewayRejectionReason() != null) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.stripe.param.CustomerUpdateParams;
|
||||||
import com.stripe.param.CustomerUpdateParams.InvoiceSettings;
|
import com.stripe.param.CustomerUpdateParams.InvoiceSettings;
|
||||||
import com.stripe.param.InvoiceListParams;
|
import com.stripe.param.InvoiceListParams;
|
||||||
import com.stripe.param.PaymentIntentCreateParams;
|
import com.stripe.param.PaymentIntentCreateParams;
|
||||||
|
import com.stripe.param.PaymentIntentRetrieveParams;
|
||||||
import com.stripe.param.PriceRetrieveParams;
|
import com.stripe.param.PriceRetrieveParams;
|
||||||
import com.stripe.param.SetupIntentCreateParams;
|
import com.stripe.param.SetupIntentCreateParams;
|
||||||
import com.stripe.param.SubscriptionCancelParams;
|
import com.stripe.param.SubscriptionCancelParams;
|
||||||
|
@ -216,12 +217,23 @@ public class StripeManager implements SubscriptionProcessorManager {
|
||||||
public CompletableFuture<PaymentDetails> getPaymentDetails(String paymentIntentId) {
|
public CompletableFuture<PaymentDetails> getPaymentDetails(String paymentIntentId) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
try {
|
try {
|
||||||
final PaymentIntent paymentIntent = stripeClient.paymentIntents().retrieve(paymentIntentId, commonOptions());
|
final PaymentIntentRetrieveParams params = PaymentIntentRetrieveParams.builder()
|
||||||
|
.addExpand("latest_charge").build();
|
||||||
|
final PaymentIntent paymentIntent = stripeClient.paymentIntents().retrieve(paymentIntentId, params, commonOptions());
|
||||||
|
|
||||||
|
ChargeFailure chargeFailure = null;
|
||||||
|
if (paymentIntent.getLatestChargeObject() != null) {
|
||||||
|
final Charge charge = paymentIntent.getLatestChargeObject();
|
||||||
|
if (charge.getFailureCode() != null || charge.getFailureMessage() != null) {
|
||||||
|
chargeFailure = createChargeFailure(charge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new PaymentDetails(paymentIntent.getId(),
|
return new PaymentDetails(paymentIntent.getId(),
|
||||||
paymentIntent.getMetadata() == null ? Collections.emptyMap() : paymentIntent.getMetadata(),
|
paymentIntent.getMetadata() == null ? Collections.emptyMap() : paymentIntent.getMetadata(),
|
||||||
getPaymentStatusForStatus(paymentIntent.getStatus()),
|
getPaymentStatusForStatus(paymentIntent.getStatus()),
|
||||||
Instant.ofEpochSecond(paymentIntent.getCreated()));
|
Instant.ofEpochSecond(paymentIntent.getCreated()),
|
||||||
|
chargeFailure);
|
||||||
} catch (StripeException e) {
|
} catch (StripeException e) {
|
||||||
if (e.getStatusCode() == 404) {
|
if (e.getStatusCode() == 404) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -479,6 +491,16 @@ public class StripeManager implements SubscriptionProcessorManager {
|
||||||
}, executor);
|
}, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ChargeFailure createChargeFailure(final Charge charge) {
|
||||||
|
Charge.Outcome outcome = charge.getOutcome();
|
||||||
|
return new ChargeFailure(
|
||||||
|
charge.getFailureCode(),
|
||||||
|
charge.getFailureMessage(),
|
||||||
|
outcome != null ? outcome.getNetworkStatus() : null,
|
||||||
|
outcome != null ? outcome.getReason() : null,
|
||||||
|
outcome != null ? outcome.getType() : null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<SubscriptionInformation> getSubscriptionInformation(Object subscriptionObj) {
|
public CompletableFuture<SubscriptionInformation> getSubscriptionInformation(Object subscriptionObj) {
|
||||||
|
|
||||||
|
@ -497,13 +519,7 @@ public class StripeManager implements SubscriptionProcessorManager {
|
||||||
if (invoice.getChargeObject() != null) {
|
if (invoice.getChargeObject() != null) {
|
||||||
final Charge charge = invoice.getChargeObject();
|
final Charge charge = invoice.getChargeObject();
|
||||||
if (charge.getFailureCode() != null || charge.getFailureMessage() != null) {
|
if (charge.getFailureCode() != null || charge.getFailureMessage() != null) {
|
||||||
Charge.Outcome outcome = charge.getOutcome();
|
chargeFailure = createChargeFailure(charge);
|
||||||
chargeFailure = new ChargeFailure(
|
|
||||||
charge.getFailureCode(),
|
|
||||||
charge.getFailureMessage(),
|
|
||||||
outcome != null ? outcome.getNetworkStatus() : null,
|
|
||||||
outcome != null ? outcome.getReason() : null,
|
|
||||||
outcome != null ? outcome.getType() : null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (charge.getPaymentMethodDetails() != null
|
if (charge.getPaymentMethodDetails() != null
|
||||||
|
|
|
@ -60,7 +60,8 @@ public interface SubscriptionProcessorManager {
|
||||||
record PaymentDetails(String id,
|
record PaymentDetails(String id,
|
||||||
Map<String, String> customMetadata,
|
Map<String, String> customMetadata,
|
||||||
PaymentStatus status,
|
PaymentStatus status,
|
||||||
Instant created) {
|
Instant created,
|
||||||
|
@Nullable ChargeFailure chargeFailure) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import java.time.Clock;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -267,6 +268,47 @@ class SubscriptionControllerTest {
|
||||||
assertThat(response.getStatus()).isEqualTo(422);
|
assertThat(response.getStatus()).isEqualTo(422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
void createBoostReceiptPaymentRequired(final ChargeFailure chargeFailure, boolean expectChargeFailure) {
|
||||||
|
when(STRIPE_MANAGER.getPaymentDetails(any())).thenReturn(CompletableFuture.completedFuture(new SubscriptionProcessorManager.PaymentDetails(
|
||||||
|
"id",
|
||||||
|
Collections.emptyMap(),
|
||||||
|
SubscriptionProcessorManager.PaymentStatus.FAILED,
|
||||||
|
Instant.now(),
|
||||||
|
chargeFailure)
|
||||||
|
));
|
||||||
|
Response response = RESOURCE_EXTENSION.target("/v1/subscription/boost/receipt_credentials")
|
||||||
|
.request()
|
||||||
|
.post(Entity.json("""
|
||||||
|
{
|
||||||
|
"paymentIntentId": "foo",
|
||||||
|
"receiptCredentialRequest": "abcd",
|
||||||
|
"processor": "STRIPE"
|
||||||
|
}
|
||||||
|
"""));
|
||||||
|
assertThat(response.getStatus()).isEqualTo(402);
|
||||||
|
|
||||||
|
if (expectChargeFailure) {
|
||||||
|
assertThat(response.readEntity(SubscriptionController.CreateBoostReceiptCredentialsErrorResponse.class).chargeFailure()).isEqualTo(chargeFailure);
|
||||||
|
} else {
|
||||||
|
assertThat(response.readEntity(String.class)).isEqualTo("{}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> createBoostReceiptPaymentRequired() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(new ChargeFailure(
|
||||||
|
"generic_decline",
|
||||||
|
"some failure message",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
), true),
|
||||||
|
Arguments.of(null, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void confirmPaypalBoostProcessorError() {
|
void confirmPaypalBoostProcessorError() {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue