killbill-aplcache

Details

diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 68eadbe..dd03756 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -248,6 +248,7 @@ public enum ErrorCode {
     PAYMENT_NO_SUCH_REFUND(7023, "Refund %s does not exist"),
     PAYMENT_NO_SUCH_SUCCESS_PAYMENT(7024, "Payment %s did not succeed"),
     PAYMENT_REFUND_AMOUNT_TOO_LARGE(7025, "Refund amount if larger than payment"),
+    PAYMENT_BAD_ACCOUNT(7026, "Account %s has payments left in an unknwon state"),
 
     PAYMENT_PLUGIN_TIMEOUT(7100, "Plugin timeout for account %s and invoice %s"),
     PAYMENT_PLUGIN_ACCOUNT_INIT(7101, "Account initialization for account %s and plugin % s failed: %s"),
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
index eea8f1f..6ff4c51 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
@@ -669,7 +669,7 @@ public class TestRepairBP extends TestApiBaseRepair {
         }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
     }
 
-    @Test(groups = "slow", enabled=false)
+    @Test(groups = "slow")
     public void testENT_REPAIR_VIEW_CHANGED_ctd() throws Exception {
         final TestWithException test = new TestWithException();
         final DateTime startDate = clock.getUTCNow();
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index 0167a71..d5fb12c 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -30,6 +30,8 @@ import java.util.concurrent.TimeoutException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.name.Named;
@@ -57,6 +59,7 @@ import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 import com.ning.billing.payment.retry.AutoPayRetryService.AutoPayRetryServiceScheduler;
 import com.ning.billing.payment.retry.FailedPaymentRetryService.FailedPaymentRetryServiceScheduler;
 import com.ning.billing.payment.retry.PluginFailureRetryService.PluginFailureRetryServiceScheduler;
+import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.BusEvent;
@@ -160,9 +163,9 @@ public class PaymentProcessor extends ProcessorBase {
                         @Override
                         public boolean apply(final PaymentModelDao in) {
                             // Payments left in AUTO_PAY_OFF or for which we did not retry enough
-                             return (in.getPaymentStatus() == PaymentStatus.AUTO_PAY_OFF ||
-                                     in.getPaymentStatus() == PaymentStatus.PAYMENT_FAILURE ||
-                                     in.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE);
+                            return (in.getPaymentStatus() == PaymentStatus.AUTO_PAY_OFF ||
+                                    in.getPaymentStatus() == PaymentStatus.PAYMENT_FAILURE ||
+                                    in.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE);
                         }
                     });
                     // Insert one retry event for each payment left in AUTO_PAY_OFF
@@ -202,6 +205,8 @@ public class PaymentProcessor extends ProcessorBase {
         }
     }
 
+
+
     public Payment createPayment(final Account account, final UUID invoiceId, final BigDecimal inputAmount, final CallContext context, final boolean isInstantPayment)
     throws PaymentApiException {
         final PaymentPluginApi plugin = getPaymentProviderPlugin(account);
@@ -222,6 +227,11 @@ public class PaymentProcessor extends ProcessorBase {
                         return null;
                     }
 
+                    final boolean isBadAccount = setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(account.getId(), context, isInstantPayment);
+                    if (isBadAccount && isInstantPayment) {
+                        throw new PaymentApiException(ErrorCode.PAYMENT_BAD_ACCOUNT, account.getId());
+                    }
+
                     final BigDecimal requestedAmount = getAndValidatePaymentAmount(invoice, inputAmount, isInstantPayment);
                     if (isAccountAutoPayOff(account.getId())) {
                         return processNewPaymentForAutoPayOffWithAccountLocked(account, invoice, requestedAmount, context);
@@ -245,6 +255,47 @@ public class PaymentProcessor extends ProcessorBase {
     }
 
 
+    private boolean setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(final UUID accountId, final CallContext context, final boolean isInstantPayment)
+    throws PaymentApiException  {
+        List<PaymentModelDao> payments =  paymentDao.getPaymentsForAccount(accountId);
+
+
+        Collection<PaymentModelDao> badPayments = Collections2.filter(payments, new Predicate<PaymentModelDao>() {
+
+            @Override
+            public boolean apply(PaymentModelDao input) {
+                return (input.getPaymentStatus() != PaymentStatus.SUCCESS &&
+                        input.getPaymentStatus() != PaymentStatus.PAYMENT_FAILURE &&
+                        input.getPaymentStatus() != PaymentStatus.AUTO_PAY_OFF);
+            }
+        });
+        if (badPayments.size() > 0) {
+
+            if (!isInstantPayment) {
+                Joiner joiner = Joiner.on(", ");
+                joiner.join(Collections2.transform(badPayments, new Function<PaymentModelDao, String>() {
+
+                    @Override
+                    public String apply(PaymentModelDao input) {
+                        return String.format("%s [%s]", input.getId(), input.getPaymentStatus());
+                    }
+                }));
+                log.warn(String.format("Setting account %s into AUTO_PAY_OFF because of bad payments : %s"), accountId, joiner.toString());
+                try {
+                    tagUserApi.addTag(accountId, ObjectType.ACCOUNT, ControlTagType.AUTO_PAY_OFF.toTagDefinition(), context);
+                } catch (TagApiException e) {
+                    log.error("Failed to add AUTO_PAY_OFF on account " + accountId, e);
+                    throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, "Failed to add AUTO_PAY_OFF on account " + accountId);
+                }
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+
     private BigDecimal getAndValidatePaymentAmount(final Invoice invoice, final BigDecimal inputAmount, final boolean isInstantPayment)
     throws PaymentApiException {
 
@@ -351,18 +402,18 @@ public class PaymentProcessor extends ProcessorBase {
     private Payment processNewPaymentForAutoPayOffWithAccountLocked(final Account account, final Invoice invoice, final BigDecimal requestedAmount, final CallContext context)
     throws PaymentApiException {
 
-    final PaymentStatus paymentStatus =  PaymentStatus.AUTO_PAY_OFF;
+        final PaymentStatus paymentStatus =  PaymentStatus.AUTO_PAY_OFF;
 
-    final PaymentModelDao paymentInfo = new PaymentModelDao(account.getId(), invoice.getId(), account.getPaymentMethodId(), requestedAmount, invoice.getCurrency(), invoice.getTargetDate(), paymentStatus);
-    final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), paymentInfo.getId(), paymentStatus, clock.getUTCNow(), requestedAmount);
+        final PaymentModelDao paymentInfo = new PaymentModelDao(account.getId(), invoice.getId(), account.getPaymentMethodId(), requestedAmount, invoice.getCurrency(), invoice.getTargetDate(), paymentStatus);
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), paymentInfo.getId(), paymentStatus, clock.getUTCNow(), requestedAmount);
 
-    paymentDao.insertPaymentWithAttempt(paymentInfo, attempt, context);
-    return new DefaultPayment(paymentInfo, Collections.singletonList(attempt));
-}
+        paymentDao.insertPaymentWithAttempt(paymentInfo, attempt, context);
+        return new DefaultPayment(paymentInfo, Collections.singletonList(attempt));
+    }
 
 
     private Payment processNewPaymentWithAccountLocked(final PaymentPluginApi plugin, final Account account, final Invoice invoice,
-                                                       final BigDecimal requestedAmount, final boolean isInstantPayment, final CallContext context) throws PaymentApiException {
+            final BigDecimal requestedAmount, final boolean isInstantPayment, final CallContext context) throws PaymentApiException {
 
 
         final PaymentModelDao payment = new PaymentModelDao(account.getId(), invoice.getId(), account.getPaymentMethodId(), requestedAmount.setScale(2, RoundingMode.HALF_EVEN), invoice.getCurrency(), invoice.getTargetDate());
@@ -401,11 +452,11 @@ public class PaymentProcessor extends ProcessorBase {
 
                 payment = paymentDao.getPayment(paymentInput.getId());
                 invoicePaymentApi.notifyOfPayment(invoice.getId(),
-                                                  payment.getAmount(),
-                                                  paymentStatus == PaymentStatus.SUCCESS ? payment.getCurrency() : null,
-                                                  payment.getId(),
-                                                  payment.getEffectiveDate(),
-                                                  context);
+                        payment.getAmount(),
+                        paymentStatus == PaymentStatus.SUCCESS ? payment.getCurrency() : null,
+                                payment.getId(),
+                                payment.getEffectiveDate(),
+                                context);
 
                 // Create Bus event
                 event = new DefaultPaymentInfoEvent(account.getId(),