Cancel past_due subscriptions immediately
This commit is contained in:
parent
815fd44ab3
commit
d135957f0d
|
@ -453,7 +453,7 @@ public class BraintreeManager implements CustomerAwareSubscriptionPaymentProcess
|
||||||
// since badge redemption is untrackable by design and unrevokable, subscription changes must be immediate and
|
// since badge redemption is untrackable by design and unrevokable, subscription changes must be immediate and
|
||||||
// not prorated. Braintree subscriptions cannot change their next billing date,
|
// not prorated. Braintree subscriptions cannot change their next billing date,
|
||||||
// so we must end the existing one and create a new one
|
// so we must end the existing one and create a new one
|
||||||
return cancelSubscriptionAtEndOfCurrentPeriod(subscription)
|
return endSubscription(subscription)
|
||||||
.thenCompose(ignored -> {
|
.thenCompose(ignored -> {
|
||||||
|
|
||||||
final Transaction transaction = getLatestTransactionForSubscription(subscription)
|
final Transaction transaction = getLatestTransactionForSubscription(subscription)
|
||||||
|
@ -504,19 +504,9 @@ public class BraintreeManager implements CustomerAwareSubscriptionPaymentProcess
|
||||||
final Instant anchor = subscription.getFirstBillingDate().toInstant();
|
final Instant anchor = subscription.getFirstBillingDate().toInstant();
|
||||||
final Instant endOfCurrentPeriod = subscription.getBillingPeriodEndDate().toInstant();
|
final Instant endOfCurrentPeriod = subscription.getBillingPeriodEndDate().toInstant();
|
||||||
|
|
||||||
boolean paymentProcessing = false;
|
final TransactionInfo latestTransactionInfo = getLatestTransactionForSubscription(subscription)
|
||||||
ChargeFailure chargeFailure = null;
|
.map(this::getTransactionInfo)
|
||||||
|
.orElse(new TransactionInfo(PaymentMethod.PAYPAL, false, false, null));
|
||||||
final Optional<Transaction> latestTransaction = getLatestTransactionForSubscription(subscription);
|
|
||||||
|
|
||||||
boolean latestTransactionFailed = false;
|
|
||||||
if (latestTransaction.isPresent()){
|
|
||||||
paymentProcessing = isPaymentProcessing(latestTransaction.get().getStatus());
|
|
||||||
if (getPaymentStatus(latestTransaction.get().getStatus()) != PaymentStatus.SUCCEEDED) {
|
|
||||||
latestTransactionFailed = true;
|
|
||||||
chargeFailure = createChargeFailure(latestTransaction.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SubscriptionInformation(
|
return new SubscriptionInformation(
|
||||||
new SubscriptionPrice(plan.getCurrencyIsoCode().toUpperCase(Locale.ROOT),
|
new SubscriptionPrice(plan.getCurrencyIsoCode().toUpperCase(Locale.ROOT),
|
||||||
|
@ -526,16 +516,31 @@ public class BraintreeManager implements CustomerAwareSubscriptionPaymentProcess
|
||||||
endOfCurrentPeriod,
|
endOfCurrentPeriod,
|
||||||
Subscription.Status.ACTIVE == subscription.getStatus(),
|
Subscription.Status.ACTIVE == subscription.getStatus(),
|
||||||
!subscription.neverExpires(),
|
!subscription.neverExpires(),
|
||||||
getSubscriptionStatus(subscription.getStatus(), latestTransactionFailed),
|
getSubscriptionStatus(subscription.getStatus(), latestTransactionInfo.transactionFailed()),
|
||||||
PaymentProvider.BRAINTREE,
|
PaymentProvider.BRAINTREE,
|
||||||
latestTransaction.map(this::getPaymentMethodFromTransaction).orElse(PaymentMethod.PAYPAL),
|
latestTransactionInfo.paymentMethod(),
|
||||||
paymentProcessing,
|
latestTransactionInfo.paymentProcessing(),
|
||||||
chargeFailure
|
latestTransactionInfo.chargeFailure()
|
||||||
);
|
);
|
||||||
|
|
||||||
}, executor);
|
}, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record TransactionInfo(
|
||||||
|
PaymentMethod paymentMethod,
|
||||||
|
boolean paymentProcessing,
|
||||||
|
boolean transactionFailed,
|
||||||
|
@Nullable ChargeFailure chargeFailure) {}
|
||||||
|
|
||||||
|
private TransactionInfo getTransactionInfo(final Transaction transaction) {
|
||||||
|
final boolean paymentProcessing = isPaymentProcessing(transaction.getStatus());
|
||||||
|
final PaymentMethod paymentMethod = getPaymentMethodFromTransaction(transaction);
|
||||||
|
if (getPaymentStatus(transaction.getStatus()) != PaymentStatus.SUCCEEDED) {
|
||||||
|
return new TransactionInfo(paymentMethod, paymentProcessing, true, createChargeFailure(transaction));
|
||||||
|
}
|
||||||
|
return new TransactionInfo(paymentMethod, paymentProcessing, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
private PaymentMethod getPaymentMethodFromTransaction(Transaction transaction) {
|
private PaymentMethod getPaymentMethodFromTransaction(Transaction transaction) {
|
||||||
if (transaction.getPayPalDetails() != null) {
|
if (transaction.getPayPalDetails() != null) {
|
||||||
return PaymentMethod.PAYPAL;
|
return PaymentMethod.PAYPAL;
|
||||||
|
@ -583,19 +588,37 @@ public class BraintreeManager implements CustomerAwareSubscriptionPaymentProcess
|
||||||
.map(com.braintreegateway.PaymentMethod::getSubscriptions)
|
.map(com.braintreegateway.PaymentMethod::getSubscriptions)
|
||||||
.orElse(Collections.emptyList())
|
.orElse(Collections.emptyList())
|
||||||
.stream()
|
.stream()
|
||||||
.map(this::cancelSubscriptionAtEndOfCurrentPeriod)
|
.map(this::endSubscription)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return CompletableFuture.allOf(subscriptionCancelFutures.toArray(new CompletableFuture[0]));
|
return CompletableFuture.allOf(subscriptionCancelFutures.toArray(new CompletableFuture[0]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<Void> endSubscription(Subscription subscription) {
|
||||||
|
final boolean latestTransactionFailed = getLatestTransactionForSubscription(subscription)
|
||||||
|
.map(this::getTransactionInfo)
|
||||||
|
.map(TransactionInfo::transactionFailed)
|
||||||
|
.orElse(false);
|
||||||
|
return switch (getSubscriptionStatus(subscription.getStatus(), latestTransactionFailed)) {
|
||||||
|
// The payment for this period has not processed yet, we should immediately cancel to prevent any payment from
|
||||||
|
// going through.
|
||||||
|
case INCOMPLETE, PAST_DUE, UNPAID -> cancelSubscriptionImmediately(subscription);
|
||||||
|
// Otherwise, set the subscription to cancel at the current period end. If the subscription is active, it may
|
||||||
|
// continue to be used until the end of the period.
|
||||||
|
default -> cancelSubscriptionAtEndOfCurrentPeriod(subscription);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private CompletableFuture<Void> cancelSubscriptionAtEndOfCurrentPeriod(Subscription subscription) {
|
private CompletableFuture<Void> cancelSubscriptionAtEndOfCurrentPeriod(Subscription subscription) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.runAsync(() -> braintreeGateway
|
||||||
braintreeGateway.subscription().update(subscription.getId(),
|
.subscription()
|
||||||
new SubscriptionRequest().numberOfBillingCycles(subscription.getCurrentBillingCycle()));
|
.update(subscription.getId(),
|
||||||
return null;
|
new SubscriptionRequest().numberOfBillingCycles(subscription.getCurrentBillingCycle())), executor);
|
||||||
}, executor);
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<Void> cancelSubscriptionImmediately(Subscription subscription) {
|
||||||
|
return CompletableFuture.runAsync(() -> braintreeGateway.subscription().cancel(subscription.getId()), executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -385,7 +385,7 @@ public class StripeManager implements CustomerAwareSubscriptionPaymentProcessor
|
||||||
}).thenCompose(subscriptions -> {
|
}).thenCompose(subscriptions -> {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
CompletableFuture<Subscription>[] futures = (CompletableFuture<Subscription>[]) subscriptions.stream()
|
CompletableFuture<Subscription>[] futures = (CompletableFuture<Subscription>[]) subscriptions.stream()
|
||||||
.map(this::cancelSubscriptionAtEndOfCurrentPeriod).toArray(CompletableFuture[]::new);
|
.map(this::endSubscription).toArray(CompletableFuture[]::new);
|
||||||
return CompletableFuture.allOf(futures);
|
return CompletableFuture.allOf(futures);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -404,7 +404,19 @@ public class StripeManager implements CustomerAwareSubscriptionPaymentProcessor
|
||||||
}, executor);
|
}, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Subscription> cancelSubscriptionImmediately(Subscription subscription) {
|
private CompletableFuture<Subscription> endSubscription(Subscription subscription) {
|
||||||
|
final SubscriptionStatus status = SubscriptionStatus.forApiValue(subscription.getStatus());
|
||||||
|
return switch (status) {
|
||||||
|
// The payment for this period has not processed yet, we should immediately cancel to prevent any payment from
|
||||||
|
// going through.
|
||||||
|
case UNPAID, PAST_DUE, INCOMPLETE -> cancelSubscriptionImmediately(subscription);
|
||||||
|
// Otherwise, set the subscription to cancel at the current period end. If the subscription is active, it may
|
||||||
|
// continue to be used until the end of the period.
|
||||||
|
default -> cancelSubscriptionAtEndOfCurrentPeriod(subscription);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<Subscription> cancelSubscriptionImmediately(Subscription subscription) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
SubscriptionCancelParams params = SubscriptionCancelParams.builder().build();
|
SubscriptionCancelParams params = SubscriptionCancelParams.builder().build();
|
||||||
try {
|
try {
|
||||||
|
@ -415,7 +427,7 @@ public class StripeManager implements CustomerAwareSubscriptionPaymentProcessor
|
||||||
}, executor);
|
}, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Subscription> cancelSubscriptionAtEndOfCurrentPeriod(Subscription subscription) {
|
private CompletableFuture<Subscription> cancelSubscriptionAtEndOfCurrentPeriod(Subscription subscription) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
SubscriptionUpdateParams params = SubscriptionUpdateParams.builder()
|
SubscriptionUpdateParams params = SubscriptionUpdateParams.builder()
|
||||||
.setCancelAtPeriodEnd(true)
|
.setCancelAtPeriodEnd(true)
|
||||||
|
|
Loading…
Reference in New Issue