killbill-aplcache

payment: force overdue refresh from InvoicePaymentControlPluginApi Due

1/18/2016 7:27:22 PM

Details

diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
index 0b45601..a7c14f8 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -29,12 +31,24 @@ import org.killbill.billing.overdue.calculator.BillingStateCalculator;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.overdue.config.api.OverdueException;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
+import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class OverdueWrapper {
 
+    private static final Logger log = LoggerFactory.getLogger(OverdueWrapper.class);
+
+    // Should we introduce a config?
+    private static final int MAX_LOCK_RETRIES = 50;
+
     private final ImmutableAccountData overdueable;
     private final BlockingInternalApi api;
+    private final GlobalLocker locker;
     private final Clock clock;
     private final OverdueStateSet overdueStateSet;
     private final BillingStateCalculator billingStateCalcuator;
@@ -43,12 +57,14 @@ public class OverdueWrapper {
     public OverdueWrapper(final ImmutableAccountData overdueable,
                           final BlockingInternalApi api,
                           final OverdueStateSet overdueStateSet,
+                          final GlobalLocker locker,
                           final Clock clock,
                           final BillingStateCalculator billingStateCalcuator,
                           final OverdueStateApplicator overdueStateApplicator) {
         this.overdueable = overdueable;
         this.overdueStateSet = overdueStateSet;
         this.api = api;
+        this.locker = locker;
         this.clock = clock;
         this.billingStateCalcuator = billingStateCalcuator;
         this.overdueStateApplicator = overdueStateApplicator;
@@ -59,6 +75,23 @@ public class OverdueWrapper {
             return overdueStateSet.getClearState();
         }
 
+        GlobalLock lock = null;
+        try {
+            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), overdueable.getId().toString(), MAX_LOCK_RETRIES);
+
+            return refreshWithLock(context);
+        } catch (final LockFailedException e) {
+            // Not good!
+            log.error(String.format("Failed to process overdue for account %s", overdueable.getId()), e);
+        } finally {
+            if (lock != null) {
+                lock.release();
+            }
+        }
+        return null;
+    }
+
+    private OverdueState refreshWithLock(final InternalCallContext context) throws OverdueException, OverdueApiException {
         final BillingState billingState = billingState(context);
         final String previousOverdueStateName = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
 
@@ -71,6 +104,22 @@ public class OverdueWrapper {
     }
 
     public void clear(final InternalCallContext context) throws OverdueException, OverdueApiException {
+        GlobalLock lock = null;
+        try {
+            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), overdueable.getId().toString(), MAX_LOCK_RETRIES);
+
+            clearWithLock(context);
+        } catch (final LockFailedException e) {
+            // Not good!
+            log.error(String.format("Failed to clear overdue for account %s", overdueable.getId()), e);
+        } finally {
+            if (lock != null) {
+                lock.release();
+            }
+        }
+    }
+
+    private void clearWithLock(final InternalCallContext context) throws OverdueException, OverdueApiException {
         final String previousOverdueStateName = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
         final OverdueState previousOverdueState = overdueStateSet.findState(previousOverdueStateName);
         overdueStateApplicator.clear(overdueable, previousOverdueState, overdueStateSet.getClearState(), context);
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
index 9c51f85..bec4c53 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -35,6 +37,7 @@ import org.killbill.billing.overdue.config.DefaultOverdueStateSet;
 import org.killbill.billing.overdue.config.api.OverdueException;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
 import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -48,11 +51,14 @@ public class OverdueWrapperFactory {
     private final BillingStateCalculator billingStateCalculator;
     private final OverdueStateApplicator overdueStateApplicator;
     private final BlockingInternalApi api;
+    private final GlobalLocker locker;
     private final Clock clock;
     private final OverdueConfigCache overdueConfigCache;
 
     @Inject
-    public OverdueWrapperFactory(final BlockingInternalApi api, final Clock clock,
+    public OverdueWrapperFactory(final BlockingInternalApi api,
+                                 final GlobalLocker locker,
+                                 final Clock clock,
                                  final BillingStateCalculator billingStateCalculator,
                                  final OverdueStateApplicator overdueStateApplicatorBundle,
                                  final OverdueConfigCache overdueConfigCache,
@@ -61,24 +67,20 @@ public class OverdueWrapperFactory {
         this.overdueStateApplicator = overdueStateApplicatorBundle;
         this.accountApi = accountApi;
         this.api = api;
+        this.locker = locker;
         this.clock = clock;
         this.overdueConfigCache = overdueConfigCache;
     }
 
-    @SuppressWarnings("unchecked")
     public OverdueWrapper createOverdueWrapperFor(final ImmutableAccountData blockable, final InternalTenantContext context) throws OverdueException {
-        return (OverdueWrapper) new OverdueWrapper(blockable, api, getOverdueStateSet(context),
-                                                   clock, billingStateCalculator, overdueStateApplicator);
+        return new OverdueWrapper(blockable, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator);
     }
 
-    @SuppressWarnings("unchecked")
     public OverdueWrapper createOverdueWrapperFor(final UUID id, final InternalTenantContext context) throws OverdueException {
-
         try {
             final ImmutableAccountData account = accountApi.getImmutableAccountDataById(id, context);
-            return new OverdueWrapper(account, api, getOverdueStateSet(context),
-                                      clock, billingStateCalculator, overdueStateApplicator);
-        } catch (AccountApiException e) {
+            return new OverdueWrapper(account, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator);
+        } catch (final AccountApiException e) {
             throw new OverdueException(e);
         }
     }
@@ -104,7 +106,7 @@ public class OverdueWrapperFactory {
             } else {
                 return ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount();
             }
-        } catch (OverdueApiException e) {
+        } catch (final OverdueApiException e) {
             throw new OverdueException(e);
         }
     }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
index d858aa1..142f39d 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,15 @@
 
 package org.killbill.billing.overdue.glue;
 
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.junction.DefaultBlockingState;
 import org.killbill.billing.mock.glue.MockAccountModule;
 import org.killbill.billing.mock.glue.MockEntitlementModule;
 import org.killbill.billing.mock.glue.MockInvoiceModule;
@@ -25,7 +34,6 @@ import org.killbill.billing.mock.glue.MockTagModule;
 import org.killbill.billing.mock.glue.MockTenantModule;
 import org.killbill.billing.overdue.TestOverdueHelper;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
-import org.killbill.billing.overdue.caching.EhCacheOverdueConfigCache;
 import org.killbill.billing.overdue.caching.MockOverdueConfigCache;
 import org.killbill.billing.overdue.caching.OverdueCacheInvalidationCallback;
 import org.killbill.billing.overdue.caching.OverdueConfigCache;
@@ -37,6 +45,8 @@ import org.killbill.billing.util.glue.AuditModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
+import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
+import org.killbill.clock.ClockMock;
 
 import com.google.inject.name.Names;
 
@@ -56,15 +66,12 @@ public class TestOverdueModule extends DefaultOverdueModule {
         install(new CustomFieldModule(configSource));
         install(new EmailModule(configSource));
         install(new MockAccountModule(configSource));
-        install(new MockEntitlementModule(configSource));
+        install(new MockEntitlementModule(configSource, new ApplicatorBlockingApi()));
         install(new MockInvoiceModule(configSource));
         install(new MockTagModule(configSource));
         install(new TemplateModule(configSource));
         install(new MockTenantModule(configSource));
-
-
-        // We can't use the dumb mocks in MockJunctionModule here
-        install(new ApplicatorMockJunctionModule(configSource));
+        install(new MemoryGlobalLockerModule(configSource));
 
         bind(OverdueBusListenerTester.class).asEagerSingleton();
         bind(TestOverdueHelper.class).asEagerSingleton();
@@ -75,4 +82,31 @@ public class TestOverdueModule extends DefaultOverdueModule {
         bind(CacheInvalidationCallback.class).annotatedWith(Names.named(OVERDUE_INVALIDATION_CALLBACK)).to(OverdueCacheInvalidationCallback.class).asEagerSingleton();
     }
 
+    public static class ApplicatorBlockingApi implements BlockingInternalApi {
+
+        private BlockingState blockingState;
+
+        public BlockingState getBlockingState() {
+            return blockingState;
+        }
+
+        @Override
+        public BlockingState getBlockingStateForService(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
+            if (blockingState != null && blockingState.getBlockedId().equals(blockableId)) {
+                return blockingState;
+            } else {
+                return DefaultBlockingState.getClearState(blockingStateType, serviceName, new ClockMock());
+            }
+        }
+
+        @Override
+        public List<BlockingState> getBlockingAllForAccount(final InternalTenantContext context) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setBlockingState(final BlockingState state, final InternalCallContext context) {
+            blockingState = state;
+        }
+    }
 }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
index 0e74a22..3f09ee6 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -25,6 +27,7 @@ import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.overdue.api.OverdueState;
+import org.killbill.billing.overdue.glue.TestOverdueModule.ApplicatorBlockingApi;
 import org.mockito.Mockito;
 import org.testng.Assert;
 
@@ -32,7 +35,6 @@ import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.entitlement.api.BlockingState;
-import org.killbill.billing.overdue.glue.ApplicatorMockJunctionModule.ApplicatorBlockingApi;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.account.api.AccountInternalApi;

payment/pom.xml 5(+5 -0)

diff --git a/payment/pom.xml b/payment/pom.xml
index 97119a8..c8e9e55 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -105,6 +105,11 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-overdue</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-platform-api</artifactId>
         </dependency>
         <dependency>
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index 69614d8..1a771c4 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -31,6 +31,9 @@ import javax.inject.Named;
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
@@ -45,6 +48,9 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.overdue.OverdueInternalApi;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.config.api.OverdueException;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
@@ -89,7 +95,9 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
     public static final String PROP_IPCD_REFUND_WITH_ADJUSTMENTS = "IPCD_REFUND_WITH_ADJUSTMENTS";
 
     private final PaymentConfig paymentConfig;
+    private final AccountInternalApi accountApi;
     private final InvoiceInternalApi invoiceApi;
+    private final OverdueInternalApi overdueApi;
     private final TagUserApi tagApi;
     private final PaymentDao paymentDao;
     private final InvoicePaymentControlDao controlDao;
@@ -100,12 +108,15 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
     private final Logger log = LoggerFactory.getLogger(InvoicePaymentControlPluginApi.class);
 
     @Inject
-    public InvoicePaymentControlPluginApi(final PaymentConfig paymentConfig, final InvoiceInternalApi invoiceApi, final TagUserApi tagApi, final PaymentDao paymentDao,
-                                          final InvoicePaymentControlDao invoicePaymentControlDao,
+    public InvoicePaymentControlPluginApi(final PaymentConfig paymentConfig, final AccountInternalApi accountApi,
+                                          final InvoiceInternalApi invoiceApi, final OverdueInternalApi overdueApi, final TagUserApi tagApi,
+                                          final PaymentDao paymentDao, final InvoicePaymentControlDao invoicePaymentControlDao,
                                           @Named(PaymentModule.RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
                                           final InternalCallContextFactory internalCallContextFactory, final Clock clock) {
         this.paymentConfig = paymentConfig;
+        this.accountApi = accountApi;
         this.invoiceApi = invoiceApi;
+        this.overdueApi = overdueApi;
         this.tagApi = tagApi;
         this.paymentDao = paymentDao;
         this.controlDao = invoicePaymentControlDao;
@@ -143,6 +154,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                                     transactionType == TransactionType.REFUND ||
                                     transactionType == TransactionType.CHARGEBACK);
 
+        boolean refreshOverdue = false;
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
         try {
             final InvoicePayment existingInvoicePayment;
@@ -162,6 +174,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                                                    paymentControlContext.getCreatedDate(),
                                                    true,
                                                    internalContext);
+                        refreshOverdue = true;
                     }
                     break;
 
@@ -174,6 +187,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                         final PluginProperty prop = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
                         final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
                         invoiceApi.createRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted, idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
+                        refreshOverdue = true;
                     }
                     break;
 
@@ -183,6 +197,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                         log.info("onSuccessCall was already completed for payment chargeback: " + paymentControlContext.getPaymentId());
                     } else {
                         invoiceApi.createChargeback(paymentControlContext.getPaymentId(), paymentControlContext.getProcessedAmount(), paymentControlContext.getProcessedCurrency(), internalContext);
+                        refreshOverdue = true;
                     }
                     break;
 
@@ -192,6 +207,11 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         } catch (final InvoiceApiException e) {
             log.error("InvoicePaymentControlPluginApi onSuccessCall failed for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType  = " + transactionType, e);
         }
+
+        if (refreshOverdue) {
+            refreshOverdue(paymentControlContext, internalContext);
+        }
+
         return new DefaultOnSuccessPaymentControlResult();
     }
 
@@ -199,6 +219,9 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
     public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> pluginProperties) throws PaymentControlApiException {
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
         final TransactionType transactionType = paymentControlContext.getTransactionType();
+
+        boolean refreshOverdue = false;
+        DateTime nextRetryDate = null;
         switch (transactionType) {
             case PURCHASE:
                 final UUID invoiceId = getInvoiceId(pluginProperties);
@@ -214,20 +237,27 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                                                    paymentControlContext.getCreatedDate(),
                                                    false,
                                                    internalContext);
-                    } catch (InvoiceApiException e) {
+                        refreshOverdue = true;
+                    } catch (final InvoiceApiException e) {
                         log.error("InvoicePaymentControlPluginApi onFailureCall failed ton update invoice for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType  = " + transactionType, e);
                     }
                 }
 
-                final DateTime nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
-                return new DefaultFailureCallResult(nextRetryDate);
+                nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
+                break;
             case REFUND:
             case CHARGEBACK:
-                // We don't retry  REFUND, CHARGEBACK
-                return new DefaultFailureCallResult(null);
+                // We don't retry REFUND, CHARGEBACK
+                break;
             default:
                 throw new IllegalStateException("Unexpected transactionType " + transactionType);
         }
+
+        if (refreshOverdue) {
+            refreshOverdue(paymentControlContext, internalContext);
+        }
+
+        return new DefaultFailureCallResult(nextRetryDate);
     }
 
     public void process_AUTO_PAY_OFF_removal(final UUID accountId, final InternalCallContext internalCallContext) {
@@ -516,4 +546,18 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
             }
         }));
     }
+
+    // See https://github.com/killbill/killbill/issues/472
+    private void refreshOverdue(final PaymentControlContext paymentControlContext, final InternalCallContext internalContext) {
+        try {
+            final Account account = accountApi.getAccountById(paymentControlContext.getAccountId(), internalContext);
+            overdueApi.refreshOverdueStateFor(account, internalContext.toCallContext(paymentControlContext.getTenantId()));
+        } catch (final AccountApiException e) {
+            log.warn("Unable to refresh overdue for accountId={}: {}", paymentControlContext.getAccountId(), e);
+        } catch (final OverdueApiException e) {
+            log.warn("Unable to refresh overdue for accountId={}: {}", paymentControlContext.getAccountId(), e);
+        } catch (final OverdueException e) {
+            log.warn("Unable to refresh overdue for accountId={}: {}", paymentControlContext.getAccountId(), e);
+        }
+    }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java b/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java
new file mode 100644
index 0000000..f98b0cf
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.glue;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.mock.glue.MockEntitlementModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Module;
+
+public class MockEntitlementModuleForPayment extends MockEntitlementModule {
+
+    public MockEntitlementModuleForPayment(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
+    @Override
+    public void installBlockingApi() {
+        super.installBlockingApi();
+
+        final BlockingState blockingState = new DefaultBlockingState(null, BlockingStateType.ACCOUNT, "clear", "test", false, false, false, new DateTime(DateTimeZone.UTC));
+        Mockito.when(blockingApi.getBlockingAllForAccount(Mockito.<InternalTenantContext>any())).thenReturn(ImmutableList.<BlockingState>of(blockingState));
+        Mockito.when(blockingApi.getBlockingStateForService(Mockito.<UUID>any(), Mockito.<BlockingStateType>any(), Mockito.anyString(), Mockito.<InternalTenantContext>any())).thenReturn(blockingState);
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
index 2b1ca1f..020de2b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -25,6 +25,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.mock.glue.MockInvoiceModule;
 import org.killbill.billing.mock.glue.MockSubscriptionModule;
 import org.killbill.billing.mock.glue.MockTenantModule;
+import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.payment.TestPaymentHelper;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPluginModule;
@@ -32,6 +33,8 @@ import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.email.EmailModule;
+import org.killbill.billing.util.email.templates.TemplateModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
@@ -73,6 +76,12 @@ public class TestPaymentModule extends PaymentModule {
         install(new MockTenantModule(configSource));
         install(new CacheModule(configSource));
         install(new CallContextModule(configSource));
+
+        install(new DefaultOverdueModule(configSource));
+        install(new TemplateModule(configSource));
+        install(new EmailModule(configSource));
+        install(new MockEntitlementModuleForPayment(configSource));
+
         installExternalApis();
         bind(TestPaymentHelper.class).asEagerSingleton();
     }
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
index 79fd448..eb68eac 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -29,13 +29,19 @@ import org.mockito.Mockito;
 
 public class MockEntitlementModule extends KillBillModule implements EntitlementModule {
 
-    private final BlockingInternalApi blockingApi = Mockito.mock(BlockingInternalApi.class);
     private final EntitlementApi entitlementApi = Mockito.mock(EntitlementApi.class);
     private final EntitlementInternalApi entitlementInternalApi = Mockito.mock(EntitlementInternalApi.class);
     private final SubscriptionApi subscriptionApi = Mockito.mock(SubscriptionApi.class);
 
+    protected final BlockingInternalApi blockingApi;
+
     public MockEntitlementModule(final KillbillConfigSource configSource) {
+        this(configSource, Mockito.mock(BlockingInternalApi.class));
+    }
+
+    public MockEntitlementModule(final KillbillConfigSource configSource, final BlockingInternalApi blockingApi) {
         super(configSource);
+        this.blockingApi = blockingApi;
     }
 
     @Override
@@ -53,7 +59,7 @@ public class MockEntitlementModule extends KillBillModule implements Entitlement
 
     @Override
     public void installBlockingApi() {
-        //bind(BlockingInternalApi.class).toInstance(blockingApi);
+        bind(BlockingInternalApi.class).toInstance(blockingApi);
     }
 
     @Override