killbill-memoizeit

Changes

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
index ed67f74..aadea54 100644
--- a/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -113,4 +113,13 @@ public class DefaultAccountInternalApi implements AccountInternalApi {
                                     final InternalCallContext context) throws AccountApiException {
         accountDao.updatePaymentMethod(accountId, paymentMethodId, context);
     }
+
+    @Override
+    public UUID getByRecordId(final Long recordId, final InternalCallContext context) throws AccountApiException {
+        final AccountModelDao accountModelDao = accountDao.getByRecordId(recordId, context);
+        if (accountModelDao == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_RECORD_ID, recordId);
+        }
+        return accountModelDao.getId();
+    }
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
index 8ab5f98..62d6cdf 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
@@ -47,4 +47,6 @@ public interface AccountDao extends EntityDao<AccountModelDao, Account, AccountA
     public void removeEmail(AccountEmailModelDao email, InternalCallContext context);
 
     public List<AccountEmailModelDao> getEmailsByAccountId(UUID accountId, InternalTenantContext context);
+
+    public AccountModelDao getByRecordId(Long recordId, InternalCallContext context);
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
index a5d6fd4..18e2ac3 100644
--- a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
@@ -215,4 +215,14 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
             }
         });
     }
+
+    @Override
+    public AccountModelDao getByRecordId(final Long recordId, final InternalCallContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<AccountModelDao>() {
+            @Override
+            public AccountModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(AccountSqlDao.class).getByRecordId(recordId, context);
+            }
+        });
+    }
 }
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
index e4ad71a..553f618 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -140,4 +140,9 @@ public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account, 
             }
         }));
     }
+
+    @Override
+    public AccountModelDao getByRecordId(final Long recordId, final InternalCallContext context) {
+        return null;
+    }
 }
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 17e40bf..a734a78 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -155,6 +155,7 @@ public enum ErrorCode {
     ACCOUNT_CANNOT_CHANGE_EXTERNAL_KEY(3005, "External keys cannot be updated. Original key remains: %s"),
     ACCOUNT_CREATION_FAILED(3006, "Account creation failed."),
     ACCOUNT_UPDATE_FAILED(3007, "Account update failed."),
+    ACCOUNT_DOES_NOT_EXIST_FOR_RECORD_ID(3008, "Account does not exist for recordId %s"),
 
     ACCOUNT_EMAIL_ALREADY_EXISTS(3500, "Account email already exists %s"),
 
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java
new file mode 100644
index 0000000..095cef7
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration.overdue;
+
+import java.math.BigDecimal;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.beatrix.integration.BeatrixIntegrationModule;
+import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.util.svcapi.junction.DefaultBlockingState;
+import com.ning.billing.util.tag.ControlTagType;
+
+import static junit.framework.Assert.assertTrue;
+
+@Test(groups = "slow")
+@Guice(modules = {BeatrixIntegrationModule.class})
+public class TestOverdueWithOverdueEnforcementOffTag extends TestOverdueBase {
+
+
+    @Override
+    public String getOverdueConfig() {
+        final String configXml = "<overdueConfig>" +
+                                 "   <bundleOverdueStates>" +
+           "       <state name=\"OD1\">" +
+                                 "           <condition>" +
+                                 "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "                   <unit>DAYS</unit><number>5</number>" +
+                                 "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "           </condition>" +
+                                 "           <externalMessage>Reached OD1</externalMessage>" +
+                                 "           <blockChanges>true</blockChanges>" +
+                                 "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                                 "           <autoReevaluationInterval>" +
+                                 "               <unit>DAYS</unit><number>5</number>" +
+                                 "           </autoReevaluationInterval>" +
+                                 "       </state>" +
+                                 "   </bundleOverdueStates>" +
+                                 "</overdueConfig>";
+        return configXml;
+    }
+
+    @Test(groups = "slow")
+    public void testNonOverdueAccountWithOverdueEnforcementOffTag() throws Exception {
+
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        // Set the OVERDUE_ENFORCEMENT_OFF tag
+        tagUserApi.addTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.OVERDUE_ENFORCEMENT_OFF.getId(), callContext);
+
+        // Set next invoice to fail and create subscription
+        paymentPlugin.makeAllInvoicesFailWithError(true);
+        final Subscription baseSubscription = createSubscriptionAndCheckForCompletion(bundle.getId(), productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseSubscription.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // DAY 30 have to get out of trial before first payment
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseSubscription.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // DAY 36 -- RIGHT AFTER OD1
+        addDaysAndCheckForCompletion(6);
+
+        // Should still be in clear state
+        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+
+        // Now remove OVERDUE_ENFORCEMENT_OFF tag
+        tagUserApi.removeTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.OVERDUE_ENFORCEMENT_OFF.getId(), callContext);
+
+        checkODState("OD1");
+    }
+
+
+
+    @Test(groups = "slow")
+    public void testOverdueAccountWithOverdueEnforcementOffTag() throws Exception {
+
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        // Set next invoice to fail and create subscription
+        paymentPlugin.makeAllInvoicesFailWithError(true);
+        final Subscription baseSubscription = createSubscriptionAndCheckForCompletion(bundle.getId(), productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseSubscription.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // DAY 30 have to get out of trial before first payment
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseSubscription.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // DAY 36 -- RIGHT AFTER OD1
+        addDaysAndCheckForCompletion(6);
+
+        // Account should be in overdue
+        checkODState("OD1");
+
+        // Set the OVERDUE_ENFORCEMENT_OFF tag
+        tagUserApi.addTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.OVERDUE_ENFORCEMENT_OFF.getId(), callContext);
+
+        // Should now be in clear state
+        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+
+        // Now remove OVERDUE_ENFORCEMENT_OFF tag
+        tagUserApi.removeTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.OVERDUE_ENFORCEMENT_OFF.getId(), callContext);
+
+        // Account should be back in overdue
+        checkODState("OD1");
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
index bf0734a..0463a50 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
@@ -17,7 +17,7 @@
 package com.ning.billing.invoice.api;
 
 import com.ning.billing.invoice.InvoiceListener;
-import com.ning.billing.invoice.TagHandler;
+import com.ning.billing.invoice.InvoiceTagHandler;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
@@ -32,11 +32,11 @@ public class DefaultInvoiceService implements InvoiceService {
     public static final String INVOICE_SERVICE_NAME = "invoice-service";
     private final NextBillingDateNotifier dateNotifier;
     private final InvoiceListener invoiceListener;
-    private final TagHandler tagHandler;
+    private final InvoiceTagHandler tagHandler;
     private final InternalBus eventBus;
 
     @Inject
-    public DefaultInvoiceService(final InvoiceListener invoiceListener, final TagHandler tagHandler, final InternalBus eventBus, final NextBillingDateNotifier dateNotifier) {
+    public DefaultInvoiceService(final InvoiceListener invoiceListener, final InvoiceTagHandler tagHandler, final InternalBus eventBus, final NextBillingDateNotifier dateNotifier) {
         this.invoiceListener = invoiceListener;
         this.tagHandler = tagHandler;
         this.eventBus = eventBus;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java
index 6651d23..ad1ccc3 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java
@@ -20,7 +20,7 @@ import org.skife.config.ConfigurationObjectFactory;
 
 import com.ning.billing.glue.InvoiceModule;
 import com.ning.billing.invoice.InvoiceListener;
-import com.ning.billing.invoice.TagHandler;
+import com.ning.billing.invoice.InvoiceTagHandler;
 import com.ning.billing.invoice.api.DefaultInvoiceService;
 import com.ning.billing.invoice.api.InvoiceMigrationApi;
 import com.ning.billing.invoice.api.InvoiceNotifier;
@@ -106,7 +106,7 @@ public class DefaultInvoiceModule extends AbstractModule implements InvoiceModul
     }
 
     protected void installTagHandler() {
-        bind(TagHandler.class).asEagerSingleton();
+        bind(InvoiceTagHandler.class).asEagerSingleton();
     }
 
     protected void installInvoiceGenerator() {
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
index a6621cd..bc2110a 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -22,14 +22,15 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.joda.time.LocalDate;
 import org.joda.time.Period;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.ning.billing.ErrorCode;
+import com.ning.billing.ObjectType;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.catalog.api.ActionPolicy;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
@@ -56,7 +57,10 @@ import com.ning.billing.util.svcapi.account.AccountInternalApi;
 import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
 import com.ning.billing.util.svcapi.junction.BlockingInternalApi;
 import com.ning.billing.util.svcapi.junction.DefaultBlockingState;
+import com.ning.billing.util.svcapi.tag.TagInternalApi;
 import com.ning.billing.util.svcsapi.bus.InternalBus;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
 
 import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
@@ -73,29 +77,37 @@ public class OverdueStateApplicator<T extends Blockable> {
     private final AccountInternalApi accountApi;
     private final EntitlementInternalApi entitlementUserApi;
     private final OverdueEmailGenerator overdueEmailGenerator;
+    final TagInternalApi tagApi;
     private final EmailSender emailSender;
 
     @Inject
     public OverdueStateApplicator(final BlockingInternalApi accessApi, final AccountInternalApi accountApi, final EntitlementInternalApi entitlementUserApi,
                                   final Clock clock, final OverdueCheckPoster poster, final OverdueEmailGenerator overdueEmailGenerator,
-                                  final EmailConfig config, final InternalBus bus) {
+                                  final EmailConfig config, final InternalBus bus, final TagInternalApi tagApi) {
         this.blockingApi = accessApi;
         this.accountApi = accountApi;
         this.entitlementUserApi = entitlementUserApi;
         this.clock = clock;
         this.poster = poster;
         this.overdueEmailGenerator = overdueEmailGenerator;
+        this.tagApi = tagApi;
         this.emailSender = new DefaultEmailSender(config);
         this.bus = bus;
     }
 
 
+
     public void apply(final OverdueState<T> firstOverdueState, final BillingState<T> billingState,
                       final T overdueable, final String previousOverdueStateName,
                       final OverdueState<T> nextOverdueState, final InternalCallContext context) throws OverdueException {
         try {
 
-            log.debug("OverdueStateApplicator <enter> : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueStateName + ", nextState = " + nextOverdueState);
+            if (isAccountTaggedWith_OVERDUE_ENFORCEMENT_OFF(context)) {
+                log.debug("OverdueStateApplicator:apply returns because account (recordId = " + context.getAccountRecordId() + ") is set with OVERDUE_ENFORCEMENT_OFF ");
+                return;
+            }
+
+            log.debug("OverdueStateApplicator:apply <enter> : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueStateName + ", nextState = " + nextOverdueState);
 
             final boolean conditionForNextNotfication = !nextOverdueState.isClearState() ||
                                                         // We did not reach the first state yet but we have an unpaid invoice
@@ -125,7 +137,7 @@ public class OverdueStateApplicator<T extends Blockable> {
         }
 
         if (nextOverdueState.isClearState()) {
-            clear(overdueable, context);
+            clearFutureNotification(overdueable, context);
         }
 
         try {
@@ -135,6 +147,21 @@ public class OverdueStateApplicator<T extends Blockable> {
         }
     }
 
+    public void clear(final T overdueable, final String previousOverdueStateName, final OverdueState<T> clearState, final InternalCallContext context) throws OverdueException {
+
+        log.debug("OverdueStateApplicator:clear : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueStateName);
+
+        storeNewState(overdueable, clearState, context);
+
+        clearFutureNotification(overdueable, context);
+
+        try {
+            bus.post(createOverdueEvent(overdueable, previousOverdueStateName, clearState.getName(), context), context);
+        } catch (Exception e) {
+            log.error("Error posting overdue change event to bus", e);
+        }
+    }
+
     private OverdueChangeInternalEvent createOverdueEvent(final T overdueable, final String previousOverdueStateName, final String nextOverdueStateName, final InternalCallContext context) throws BlockingApiException {
         return new DefaultOverdueChangeEvent(overdueable.getId(), Blockable.Type.get(overdueable), previousOverdueStateName, nextOverdueStateName, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
     }
@@ -148,7 +175,7 @@ public class OverdueStateApplicator<T extends Blockable> {
                                                                   blockChanges(nextOverdueState),
                                                                   blockEntitlement(nextOverdueState),
                                                                   blockBilling(nextOverdueState)),
-                                                                  context);
+                                         context);
         } catch (Exception e) {
             throw new OverdueException(e, ErrorCode.OVERDUE_CAT_ERROR_ENCOUNTERED, blockable.getId(), blockable.getClass().getName());
         }
@@ -170,7 +197,7 @@ public class OverdueStateApplicator<T extends Blockable> {
         poster.insertOverdueCheckNotification(overdueable, timeOfNextCheck, context);
     }
 
-    protected void clear(final T blockable, final InternalCallContext context) {
+    protected void clearFutureNotification(final T blockable, final InternalCallContext context) {
         // Need to clear the override table here too (when we add it)
         poster.clearNotificationsFor(blockable, context);
     }
@@ -275,4 +302,24 @@ public class OverdueStateApplicator<T extends Blockable> {
             log.warn(String.format("Unable to generate overdue notification email for account %s and overdueable %s", account.getId(), overdueable.getId()), e);
         }
     }
+
+    //
+    // Uses context information to retrieve account matching the Overduable object and check whether we should do any overdue processing
+    //
+    private boolean isAccountTaggedWith_OVERDUE_ENFORCEMENT_OFF(final InternalCallContext context) throws OverdueException {
+
+        try {
+            final UUID accountId = accountApi.getByRecordId(context.getAccountRecordId(), context);
+
+            final List<Tag> accountTags = tagApi.getTags(accountId, ObjectType.ACCOUNT, context);
+            for (Tag cur : accountTags) {
+                if (cur.getTagDefinitionId().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.getId())) {
+                    return true;
+                }
+            }
+            return false;
+        } catch (AccountApiException e) {
+            throw new OverdueException(e);
+        }
+    }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java
index 3340a49..5d5bdb0 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java
@@ -54,6 +54,13 @@ public class OverdueDispatcher {
         }
     }
 
+    public void clearOverdueForAccount(final UUID accountId, final InternalCallContext context) {
+        final List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(accountId, context);
+        for (final SubscriptionBundle bundle : bundles) {
+            clearOverdue(Type.SUBSCRIPTION_BUNDLE, bundle.getId(), context);
+        }
+    }
+
     public void processOverdue(final Blockable.Type type, final UUID blockableId, final InternalCallContext context) {
         try {
             factory.createOverdueWrapperFor(type, blockableId, context).refresh(context);
@@ -61,4 +68,12 @@ public class OverdueDispatcher {
             log.error(String.format("Error processing Overdue for blockable %s (type %s)", blockableId, type), e);
         }
     }
+
+    public void clearOverdue(final Blockable.Type type, final UUID blockableId, final InternalCallContext context) {
+        try {
+            factory.createOverdueWrapperFor(type, blockableId, context).clear(context);
+        } catch (BillingExceptionBase e) {
+            log.error(String.format("Error processing Overdue for blockable %s (type %s)", blockableId, type), e);
+        }
+    }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java
index 1733da5..52ed173 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java
@@ -21,14 +21,18 @@ import java.util.UUID;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.ning.billing.ObjectType;
 import com.ning.billing.ovedue.notification.OverdueCheckNotificationKey;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
 import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.events.ControlTagCreationInternalEvent;
+import com.ning.billing.util.events.ControlTagDeletionInternalEvent;
 import com.ning.billing.util.events.InvoiceAdjustmentInternalEvent;
 import com.ning.billing.util.events.PaymentErrorInternalEvent;
 import com.ning.billing.util.events.PaymentInfoInternalEvent;
+import com.ning.billing.util.tag.ControlTagType;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
@@ -48,6 +52,23 @@ public class OverdueListener {
     }
 
     @Subscribe
+    public void handle_OVERDUE_ENFORCEMENT_OFF_Insert(final ControlTagCreationInternalEvent event) {
+        if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
+            final UUID accountId = event.getObjectId();
+            dispatcher.clearOverdueForAccount(accountId, createCallContext(event.getUserToken(), event.getAccountRecordId(), event.getTenantRecordId()));
+        }
+    }
+
+    @Subscribe
+    public void handle_OVERDUE_ENFORCEMENT_OFF_Removal(final ControlTagDeletionInternalEvent event) {
+        if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
+            final UUID accountId = event.getObjectId();
+            dispatcher.processOverdueForAccount(accountId, createCallContext(event.getUserToken(), event.getAccountRecordId(), event.getTenantRecordId()));
+        }
+    }
+
+
+    @Subscribe
     public void handlePaymentInfoEvent(final PaymentInfoInternalEvent event) {
         log.debug("Received PaymentInfo event {}", event);
         dispatcher.processOverdueForAccount(event.getAccountId(), createCallContext(event.getUserToken(), event.getAccountRecordId(), event.getTenantRecordId()));
diff --git a/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
index d663978..f40db15 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
@@ -64,6 +64,11 @@ public class OverdueWrapper<T extends Blockable> {
         return nextOverdueState;
     }
 
+    public void clear(final InternalCallContext context) throws OverdueException, OverdueApiException {
+        final String previousOverdueStateName = api.getBlockingStateFor(overdueable, context).getStateName();
+        overdueStateApplicator.clear(overdueable, previousOverdueStateName, overdueStateSet.getClearState(), context);
+    }
+
     public BillingState<T> billingState(final InternalTenantContext context) throws OverdueException {
         return billingStateCalcuator.calculateBillingState(overdueable, context);
     }
diff --git a/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java b/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
index ff3a672..4e283e1 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
@@ -65,6 +65,7 @@ import com.ning.billing.util.globallocker.GlobalLocker;
 import com.ning.billing.util.globallocker.MySqlGlobalLocker;
 import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.svcapi.account.AccountInternalApi;
 import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
@@ -114,7 +115,8 @@ public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
 
     @BeforeClass(groups = "slow")
     public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException, EntitlementUserApiException, AccountApiException {
-        final Injector g = Guice.createInjector(Stage.PRODUCTION, new MockInvoiceModule(), new MockPaymentModule(), new BusModule(), new DefaultOverdueModule() {
+        final Injector g = Guice.createInjector(Stage.PRODUCTION, new MockInvoiceModule(), new MockPaymentModule(), new BusModule(), new TagStoreModule(),
+                                                new DefaultOverdueModule() {
             @Override
             protected void configure() {
                 super.configure();
diff --git a/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java b/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java
index 78ff704..e6a3a8e 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java
@@ -60,6 +60,7 @@ import com.ning.billing.util.email.EmailModule;
 import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.glue.CallContextModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
 import com.ning.billing.util.svcapi.account.AccountInternalApi;
 import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
@@ -71,7 +72,7 @@ import com.google.inject.Inject;
 
 @Guice(modules = {DefaultOverdueModule.class, OverdueListenerTesterModule.class, MockClockModule.class, ApplicatorMockJunctionModule.class,
                   CallContextModule.class, CatalogModule.class, MockInvoiceModule.class, MockPaymentModule.class, NotificationQueueModule.class,
-                  EmailModule.class, TemplateModule.class, TestDbiModule.class, MockEntitlementModule.class, MockInvoiceModule.class, MockAccountModule.class})
+                  EmailModule.class, TemplateModule.class, TestDbiModule.class, TagStoreModule.class, MockEntitlementModule.class, MockInvoiceModule.class, MockAccountModule.class})
 public abstract class OverdueTestBase extends OverdueTestSuiteWithEmbeddedDB {
     protected final String configXml =
             "<overdueConfig>" +
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 d3a06d3..ed3946f 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
@@ -310,7 +310,7 @@ public class PaymentProcessor extends ProcessorBase {
     }
 
     public void retryFailedPayment(final UUID paymentId, final InternalCallContext context) {
-        log.info("STEPH retrying failed payment " + paymentId + " time = " + clock.getUTCNow());
+        log.info("Retrying failed payment " + paymentId + " time = " + clock.getUTCNow());
         retryFailedPaymentInternal(paymentId, context, PaymentStatus.PAYMENT_FAILURE);
     }
 
@@ -450,7 +450,6 @@ public class PaymentProcessor extends ProcessorBase {
                 break;
 
             case ERROR:
-                log.info("STEPH payment failure...");
                 allAttempts = paymentDao.getAttemptsForPayment(paymentInput.getId(), context);
                 // Schedule if non instant payment and max attempt for retry not reached yet
                 if (!isInstantPayment) {
@@ -510,7 +509,7 @@ public class PaymentProcessor extends ProcessorBase {
 
         final boolean isScheduledForRetry = failedPaymentRetryService.scheduleRetry(paymentId, retryAttempt);
 
-        log.info("STEPH scheduleRetryOnPaymentFailure id = " + paymentId + ", retryAttempt = " + retryAttempt + ", retry :" + isScheduledForRetry);
+        log.debug("scheduleRetryOnPaymentFailure id = " + paymentId + ", retryAttempt = " + retryAttempt + ", retry :" + isScheduledForRetry);
 
         return isScheduledForRetry ? PaymentStatus.PAYMENT_FAILURE : PaymentStatus.PAYMENT_FAILURE_ABORTED;
     }
diff --git a/payment/src/main/java/com/ning/billing/payment/glue/DefaultPaymentService.java b/payment/src/main/java/com/ning/billing/payment/glue/DefaultPaymentService.java
index 81f0ef5..3c24f10 100644
--- a/payment/src/main/java/com/ning/billing/payment/glue/DefaultPaymentService.java
+++ b/payment/src/main/java/com/ning/billing/payment/glue/DefaultPaymentService.java
@@ -25,7 +25,7 @@ import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentService;
 import com.ning.billing.payment.bus.InvoiceHandler;
-import com.ning.billing.payment.bus.TagHandler;
+import com.ning.billing.payment.bus.PaymentTagHandler;
 import com.ning.billing.payment.retry.AutoPayRetryService;
 import com.ning.billing.payment.retry.FailedPaymentRetryService;
 import com.ning.billing.payment.retry.PluginFailureRetryService;
@@ -40,7 +40,7 @@ public class DefaultPaymentService implements PaymentService {
     public static final String SERVICE_NAME = "payment-service";
 
     private final InvoiceHandler invoiceHandler;
-    private final TagHandler tagHandler;
+    private final PaymentTagHandler tagHandler;
     private final InternalBus eventBus;
     private final PaymentApi api;
     private final FailedPaymentRetryService failedRetryService;
@@ -49,7 +49,7 @@ public class DefaultPaymentService implements PaymentService {
 
     @Inject
     public DefaultPaymentService(final InvoiceHandler invoiceHandler,
-            final TagHandler tagHandler,
+            final PaymentTagHandler tagHandler,
             final PaymentApi api, final InternalBus eventBus,
             final FailedPaymentRetryService failedRetryService,
             final PluginFailureRetryService timedoutRetryService,
diff --git a/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
index 906a0b6..f85e86e 100644
--- a/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
@@ -25,6 +25,7 @@ import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.config.SimplePropertyConfigSource;
 
+import com.ning.billing.payment.bus.PaymentTagHandler;
 import com.ning.billing.payment.dao.DefaultPaymentDao;
 import com.ning.billing.util.config.PaymentConfig;
 import com.ning.billing.payment.api.DefaultPaymentApi;
@@ -32,7 +33,6 @@ import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentService;
 import com.ning.billing.payment.api.svcs.DefaultPaymentInternalApi;
 import com.ning.billing.payment.bus.InvoiceHandler;
-import com.ning.billing.payment.bus.TagHandler;
 import com.ning.billing.payment.core.PaymentMethodProcessor;
 import com.ning.billing.payment.core.PaymentProcessor;
 import com.ning.billing.payment.core.RefundProcessor;
@@ -114,7 +114,7 @@ public class PaymentModule extends AbstractModule {
         bind(PaymentInternalApi.class).to(DefaultPaymentInternalApi.class).asEagerSingleton();
         bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
         bind(InvoiceHandler.class).asEagerSingleton();
-        bind(TagHandler.class).asEagerSingleton();
+        bind(PaymentTagHandler.class).asEagerSingleton();
         bind(PaymentService.class).to(DefaultPaymentService.class).asEagerSingleton();
         installPaymentProviderPlugins(paymentConfig);
         installPaymentDao();
diff --git a/util/src/main/java/com/ning/billing/util/svcapi/account/AccountInternalApi.java b/util/src/main/java/com/ning/billing/util/svcapi/account/AccountInternalApi.java
index 60b36b1..b86da74 100644
--- a/util/src/main/java/com/ning/billing/util/svcapi/account/AccountInternalApi.java
+++ b/util/src/main/java/com/ning/billing/util/svcapi/account/AccountInternalApi.java
@@ -40,4 +40,6 @@ public interface AccountInternalApi {
     public void removePaymentMethod(UUID accountId, InternalCallContext context) throws AccountApiException;
 
     public void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException;
+
+    public UUID getByRecordId(Long recordId, InternalCallContext context) throws AccountApiException;
 }